1. Introduction

One of our teammates lives in Hamilton Heights, in an ‘up and coming’ area called Sugar Hill which has seen a lot of new bars and restaurants open up (we highly suggest checking out Harlem Public). We were wondering how one could easily identify an ‘up and coming’ area before all the land values rise. Could they be identified by new winebars and pubs? Or perhaps by sidewalk cafes that pop up as soon as the weather gets warmer?

We found a dataset for liquor licenses in New York State at data.ny.gov. Called “Liquor Authority Quarterly List of Active Licenses”, this dataset gives information about all of the currently active liquor licenses as of April, 2017.

We also found a dataset about all requested and active sidewalk cafe license applications in New York City (and the surrounding boroughs excluding Long Island) called “Sidewalk Caf? Licenses and Applications”. This has also been updated in April, 2017.

Finally, we wanted to know whether there is a clear correlation between the average income of neighborhood inhabitants and the number of sidewalk cafes and bars in that area. To do this, we used the following datasets:

We realized that there is no authoritative datasource mapping each zip code to a single neighborhood. In order to be able to map neighborhood locations on a map, we wrote a python scraper to pull zipcode to neighborhood data from the following locations, and then joined it with the zipcode to location data:

2. Team

  1. Adam Coviensky - in charge of the income per zipcode data
  1. Rohan Pitre -
  1. Marika Lohmus -
require(ggplot2)
require(dplyr)
require(tidyr)
require(extracat)
require(forcats)
require(ggmosaic)
require(ggmap)
require(gridExtra)
require(ggrepel)
require(lubridate)
require(shiny)
require(zipcode)
require(viridis)
require(rgdal)
require(maptools)
require(ggthemes)

3. Analysis of Data Quality

Zip Code, Neighborhood and Land Value Data

We realized that the original data was not going to be sufficient. We only had a few rental observations from Staten Island, Queens and the Bronx, and all of the observations were directly bordering Manhattan. We decided to find new data. We found the data for a Shiny project titled Superzip with Income per zipcode for the entire United States. Since we also wanted the income per neighborhood, we found a mapping from zipcode to neighborhood name that we will use for our final observations. It proved rather difficult to actually clean this mapping.

superzip <- read.table('Data/superzip.csv', header = TRUE)
neighborhood <- read.csv('Data/nyc_zcta.csv')
colnames(neighborhood)[1] <- "zipcode"
neighborhood2 <- neighborhood[,c(1, 6)]
neighborhood2$zipcode <- as.factor(neighborhood2$zipcode)
superzip2 <- superzip %>% dplyr::filter(state == "NY")
superzip3 <- superzip2 %>% filter(city %in% c("Brooklyn", "Queens Village", "Staten Island", "New York", "Bronx"))
superzip3$city[superzip3$city=="New York"] = "Manhattan"

Without filtering for the state == NY, we found we had some extra rows. When we looked up these zipcodes online, we found that there were 6 other Brooklyns in the United States. Thus, we had to first filter for NY, then only containing the boroughs. Then, we changed the borough from New York to be Manhattan

master_zip <- full_join(superzip3, neighborhood2, by = 'zipcode')
joining factors with different levels, coercing to character vector
colnames(master_zip)[11] <- "area"

This showed me the rows which have NAs. As we can clearly see, there were more zipcodes in the neighborhood data than in the superzip data. Our way of adjusting for this will be to determine an average income for each neighborhood. Then, we will be imputing the missing incomes using the average incomes for the zipcodes which we do have in that neighborhood.

AVG_INCOME_NEIGHBORHOOD <- master_zip %>% group_by(area) %>% summarise(avg = mean(income., na.rm=TRUE))

Upon viewing the output, we still have many NAs in the avg column, when looking at the master_zip dataframe filtered for “Astoria & Long Island City”, which is the first neighborhood with an avg of NA, we see that it’s because we have no income data for any zipcodes in this area. Therefore, once we join the average income with the master_zip dataframe we will drop these rows with NAs.

master_zip2 <- left_join(master_zip, AVG_INCOME_NEIGHBORHOOD, by = "area")

Here we have joined the average income per neighborhood data back to the rest of the income data.

data(zipcode)
census <- merge(master_zip2, zipcode, by.x = 'zipcode', by.y = 'zip')
census <- census %>% filter(!is.na(avg))

Here we are using the package zipcode to map all of our zipcodes to geospatial coordinates so that we can plot them using ggmap. Then, we are filtering out all of the zipcodes and neighborhoods for which we have no income data at all within that neighborhood.

missing_zips <- c(10065, 10075, 10106, 10281, 11101, 11102, 11103, 11104, 11105, 11106, 11109, 11249, 11366, 11367, 11368, 11370, 11372, 11373, 11374, 11375, 11377, 11379, 11435, 11694)
filtered_missing_zips <- filter(superzip, zipcode %in% missing_zips)
setdiff(missing_zips, filtered_missing_zips$zipcode)
[1] 10065 10075 10106 10281 11109 11249

Here, Marika had informed me that we had missing zipcodes in the final census dataframe. Therefore, I went back to the superzip dataset and found that 18 of the 24 zipcodes appeared there. However, they were labeled as being in different cities, not one of the five boroughs. Since she has data in these zipcodes, we will be adding them back into the census dataframe. The 18 zipcodes which we had data on corresponded mostly to data in Queens which were not labelled as Queens. This also explains the lack of any income data in some of the neighborhoods.

We also found that 6 zipcodes are still missing from our income data. These are 10065 (Upper East Side), 10075(Upper East Side), 10106(midtown), 10281 (World Trade), 11109 (Long Island) and 11249(Williamsburg).

superzip4 <- rbind(superzip3, filtered_missing_zips)
master_zip_added <- full_join(superzip4, neighborhood2, by = 'zipcode')
joining factors with different levels, coercing to character vector
colnames(master_zip_added)[11] <- "area"
AVG_INCOME_NEIGHBORHOOD_ADDED <- master_zip_added %>% group_by(area) %>% summarise(avg = mean(income., na.rm=TRUE))
master_zip2_added <- left_join(master_zip_added, AVG_INCOME_NEIGHBORHOOD_ADDED, by = "area")
census_added <- merge(master_zip2_added, zipcode, by.x = 'zipcode', by.y = 'zip')
census_added2 <- census_added %>% filter(!is.na(avg))
write.csv(census_added2, file = "zip_master_no_missing.csv")

Here I went back and added in the missing zip codes. I then had to recompute the average statistic accounting for the new zipcodes. I repeated the same process as before once I added the zipcodes back in.

Now we are loading back in the good data after writing some to the folder.

census <- read.csv("Data/zips_master_no_missing_nbrh.csv")
ggplot(census, aes(reorder(city.x, -avg, FUN = median), avg)) + geom_boxplot(varwidth = TRUE) + ggtitle("Boxplots of Income by Borough") + labs(x="Borough", y="Average Income per neighborhood") + coord_flip()+theme_fivethirtyeight()

Here we see the distributions of the average income per area in each of the 5 boroughs. This plot appears to show that Queens Villge does not have very much data in it as this is a variable width boxplot. However, it turns out it is because all of the other single point neighborhoods are actually a part of Queens. This is a problem we had to fix. These are the 18 zipcodes we added back into the data.

census$city.x[!(census$city.y %in% c("Brooklyn", "New York", "Bronx", "Staten Island"))] <- "Queens Village"
census$city.y[!(census$city.y %in% c("Brooklyn", "New York", "Bronx", "Staten Island"))] <- "Queens Village"
ggplot(census, aes(reorder(city.x, -avg, FUN = median), avg)) + geom_boxplot(varwidth = TRUE) + ggtitle("Boxplots of Income by Borough") + labs(x="Borough", y="Average Income per neighborhood")+theme_fivethirtyeight()

Also, there is very little spread in the distribution for Bronx and Brooklyn compared to Manhattan, which has a massive spread. The missing values occur from zipcodes which occur in the neighborhood data and not in the superzip data. We can see that the average income calculated for these zipcodes in general are high. We can thus infer they are likely from New York and looking back at the census data and filtering for the NAs, which was indeed the case.

ggplot(census, aes(reorder(city.y, -avg, FUN = median), avg)) + geom_boxplot(varwidth = TRUE) + ggtitle("Boxplots of Income by Borough") + labs(x="Borough", y="Average Income per neighborhood")+theme_fivethirtyeight()

Here we see with the plot with the null values removed.

census %>% filter(!is.na(census$city.x)) %>% ggplot(aes(city.x, income., color=city.x, alpha(0.1))) + geom_point(size = 2, shape = 1) + guides(color=FALSE) + ggtitle("Strip Plot of Incomes by Borough") + labs(x="Borough", y="Income")+theme_fivethirtyeight()

Similarly to the boxplot, this shows the distribution of the income for each of the boroughs. We can see we do not have too much data on Staten Island again.

ggplot(census, aes(x=reorder(city.x, -table(city.x)[city.x]))) + geom_bar() + xlab("City") + ylab("Number of Zipcodes") + ggtitle("Number of zipcodes with observations per borough")+theme_fivethirtyeight()

Again the NAs here correspond to not having an actual income value for said zipcode. It was derived from the average for that neighborhood. This shows that approximately 25 of the zipcodes had no income data and we were forced to impute it with the average for that neighborhood. Also, this data is certainly better than the last set, although ideally we would still have more data on Queens and Staten Island especially if there are many liquor licenses and sidewalk cafes popping up in zipcodes which we are missing.

Sidewalk Cafe License Data

queens_names<-read.csv("Data/QU_Locations.csv",strip.white=TRUE)
brooklyn_names<-read.csv("Data/BK_Locations.csv",strip.white=TRUE)
manhattan_names<-read.csv("Data/MA_Locations.csv",strip.white=TRUE)
bronx_names<-read.csv("Data/BX_Locations.csv",strip.white=TRUE)
sidewalks<-read.csv("Data/Sidewalk Cafes/Sidewalk_Licenses.csv",strip.white=TRUE, na.strings=c("","NA"))
Data Quality

One of the most glaring issues in data quality is the naming of the cities in Manhattan and Queens. Capitalization differences like between “NEW YORK” and “New York” grouped cafes in the same city to be categorized differently. Similarly, cities in Queens had some abbreviation differences like between “LONG ISLAND CITY” and “LONG IS CITY” or “JACKSON HEIGHTS” and “JACKSON HTS”. I manually replaced all abbreviated or non-capitalized cities with their longer, capitalized forms.

sidewalks$CITY[sidewalks$CITY=="LONG IS CITY"]<-"LONG ISLAND CITY"
sidewalks$CITY[sidewalks$CITY=="MIDDLE VLG"]<-"MIDDLE VILLAGE"
sidewalks$CITY[sidewalks$CITY=="JACKSON HTS"]<-"JACKSON HEIGHTS"
sidewalks$CITY[sidewalks$CITY=="New York"]<-"NEW YORK"

The second data quality issue I encountered was the longitude and latitude of two restaurants. Plotting all restaurants using qmplot, I got the following result:

qmplot(LONGITUDE,LATITUDE,data=sidewalks,maptype="toner-lite",color=I("purple"))

Note that there is a mysterious dot all the way west of Harrisburg, PA. This should not be the case since the data should only apply to the City and boroughs of New York, so I plotted the longitude and latitude to see any outliers.

lat<-ggplot(sidewalks, aes(x=LATITUDE))+geom_histogram(binwidth=.01)+ggtitle("Latitude Histogram")+theme_fivethirtyeight()
long<-ggplot(sidewalks,aes(x=LONGITUDE))+geom_histogram(binwidth=.01)+ggtitle("Longitude Histogram")+theme_fivethirtyeight()
grid.arrange(lat,long,nrow=2)

You can barely see some points beow the 40.25 latitude and -77 longitude, which seem to be quite off from the rest. Next, I zoomed in on those values:

long<-ggplot(sidewalks,aes(x=LONGITUDE))+geom_histogram(binwidth=.25)+ggtitle("Zoomed Longitude Histogram")+xlim(-80,-77)
long

There are two datapoints that have an abnormally low Latitude and Longitude as compared to the rest of the data. Taking a look at these data points, I identified them as the following businesses:

sidewalks %>% filter(LONGITUDE < -77) %>% select(BUSINESS_NAME2,LONGITUDE,LATITUDE)

Looking these locations up on Google, it looks like there was an error in entering their longitude and latitude. The correct values are (40.72017,-73.9968) for Mulberry Street Bar and (40.79593,-73.9357) for Prime One 16. I have corrected these values in another CSV, which I will use from here on.

sidewalks<-read.csv("Data/Sidewalk Cafes/Sidewalk_Licenses2.csv",strip.white=TRUE, na.strings=c("","NA"))
sidewalks$CITY[sidewalks$CITY=="LONG IS CITY"]<-"LONG ISLAND CITY"
sidewalks$CITY[sidewalks$CITY=="MIDDLE VLG"]<-"MIDDLE VILLAGE"
sidewalks$CITY[sidewalks$CITY=="JACKSON HTS"]<-"JACKSON HEIGHTS"
sidewalks$CITY[sidewalks$CITY=="New York"]<-"NEW YORK"
qmplot(LONGITUDE,LATITUDE,data=sidewalks,maptype="toner-lite",color=I("purple"))

Now that the offending datapoints have been fixed, the qmplot accurately zooms in on the New York area. However, since those two data points exist, it is entirely possible that there are other mis-mapped langitude and longitude points. However, without manually going through each data point, it is not easy to deduce where.

Missing Data

To further investigate data quality, I took a look at the main columns that identified a business - its license number, license status, business name (broken into two), building number, street, city, state and zip. To display this data, I used a visna plot which highlights any columns with missing values and shows the frequency of the combinations of those columns.

visna(sidewalks[,1:9])

The visna shows that only two variables have missing values - license number and business name number 2. These are expected - only licenses that have been approved would have a license number, and some businesses do not need to use a second line for their business name.

Next, I looked at additional application information which includes the sidewalk cafe type, the square footage requested, number of tables, number of chairs, Department of Health and Mental Hygiene (DOHMH) identification number, the restaurant’s longitude and latitude, the community district and city council district it belongs to, and the URL for the website of the NYC Community District.

visna(sidewalks[,10:19])

Two variables have missing values - the square footage of the sidewalk cafe and the Department of Health and Mental Hygiene identification number. These are items that the restaurant would have to provide during their application process, and may not have been available at submission. These missing values should not impede with our analysis. However, if we wanted to cross-reference the health grades from the DOHMH datasets, we would have issues with the missing ID numbers.

The next set of columns to analyze is the set of application-specific fields. These include the application ID, and the sidewalk cafe type, square footage, number of tables and chairs provided by the applicant. It has the app status, the date at which the app status was reached, expiration date, the expiration date of the temporary operating order(“TOO” - if available), the application submit date, and whether the application has been received.

visna(sidewalks[,20:29])

There seem to be a few missing fields in the provided sidewalk cafe squarefootage data. However, we will not be focusing on this data point in our analysis. Expiration dates are also missing from certain licenses. Filtering out those licenses and looking at their APP_STATUS, we can see that those licenses that are in review may not have an assigned expiration status. Indeed, this makes sense with new applications that have not been given an expiration date.

missing_expiration<-sidewalks %>% filter(is.na(EXPIRATION_DATE)) %>% select(APP_STATUS)
missing_expiration %>% unique()

A similar explanation arises about the applications with a missing Temporary Operating Order (“TOO”) date - only those applications that have been granted a TOO in cases where the old license has expired but the renewal is in the process of being reviewed.

The remaining colums track the status of the license approval process through various stages and will not be used in this analysis. Therefore, I will not spend time on investigating any missing data.

Combining with Neighborhood data

From Adam’s analysis, we have a master_zip list of all of the zip codes mapped to the neighborhoods in each borough. I will create a separate dataframe called sidewalks_nbh which is joined with the master_zip on Zip Code. I will use the neighborhood column, ‘nbh’, to group the sidewalk cafes by borough and neighborhood. I then filtered the resulting dataframe to find any missing values from the join.

master_zip<-read.csv("Data/zips_master_no_missing_nbrh.csv", strip.white=TRUE)
sidewalks_nbh <- merge(sidewalks, master_zip, by="ZIP", all.x=TRUE)
sidewalks_nbh %>% filter(is.na(nbh)) %>% select(ZIP)

There were a lot of missing values originally from the master_zip analysis, which I have manually researched and filled in in the _nbrh.csv. Now there are no Cafes without a neighborhood designation.

Liquor License Data

ny_liquor_licenses <- read.csv('Data/liquor_licenses/active_liquor.csv')

This is a dataframe where each row corresponds to an active liquor license. There are several columns identifying the type of liquor license, the date the license is effective, as well as some geographic information.

As a sanity check, I wanted to make sure that this dataset was only for NY state data.

levels(ny_liquor_licenses$State)
 [1] ""   "AR" "AZ" "CA" "CO" "CT" "GA" "HI" "ID" "IL" "MA" "MD" "ME" "MI" "MN" "MO" "MT" "NC" "NJ" "NM" "NY" "OH"
[23] "OR" "PA" "SC" "TN" "TX" "VA" "VT" "WA" "WI" "WY"

Much to my surprise, it seems that all states are included!

ny_liquor_licenses %>% filter(State != "NY") %>% group_by(License.Type.Name) %>% summarise(count = n())

Out of the 1280 records outside of New York 1250 of them correspond to direct wine shipments which makes sense.

ny_liquor_licenses %>% filter(License.Type.Name == 'MASTER FOLDER STATUS RECORD') %>% head %>% 
  select(Premises.Name)

Through inspection of the dataframe, I noticed a few things. First, a lot of these MASTER FOLDER STATUS RECORD licenses correspond to supermarkets and big chains. But what appears more troubling is that there might be records outside of New York City.

ny_liquor_licenses %>% group_by(City) %>% 
  summarise(num = n()) %>% filter(num > 100) %>% 
  ggplot(., aes(x=fct_infreq(City), y=num)) + geom_bar(stat = 'identity') + coord_flip()+theme_fivethirtyeight()

Indeed, this dataset includes liquor licenses across the entire state. Furthermore, these don’t correspond to shipments into New York City either.

ny_liquor_licenses %>% filter(City == 'ALBANY') %>% filter(License.Type.Name == 'GROCERY STORE BEER') %>% head %>% 
  select(Premises.Name, License.Serial.Number)

In order to make this analysis consistent with the other datasets, I will use the zipcode column. Even in the plot above, we can see city names like Jackson Heights, and Astoria, which are traditionally thought of as part of New York. Therefore, filtering by city name will be difficult. Luckily, Marika and Adam were able to generate a master zip file for all neighborhoods in New York. Therefore, I will merge this dataset with the master dataset. First, as a sanity check, all zip codes should have 5 or 9 characters:

sum(is.na(ny_liquor_licenses$Zip))
[1] 0
ny_liquor_licenses %>% filter(nchar(as.character(Zip)) != 5) %>% filter(nchar(as.character(Zip)) != 9) %>% 
  select(Zip, Actual.Address.of.Premises..Address1., City)

There seem to be three zip codes with typos, which I manually googled and confirmed that the zip codes were one character off. Hence, I will replace these zip codes by the proper ones. Then I will take the first five characters of every zip code to keep everything standard. Then, I will be able to merge with the zip codes that Marika and Adam compiled.

zips <- levels(ny_liquor_licenses$Zip)
zips[zips == "112209"] <- "11209"
zips[zips == "1238"] <- "11238"
zips[zips == "1369"] <- "11369"
levels(ny_liquor_licenses$Zip) <- zips
ny_liquor_licenses$Zip <- as.character(ny_liquor_licenses$Zip)
ny_liquor_licenses <- ny_liquor_licenses %>% mutate(Zip, mod_zip = substr(Zip, 1, 5))
ny_liquor_licenses$mod_zip <- as.numeric(ny_liquor_licenses$mod_zip)
zipcodes <- read.csv('Data/zips_master_no_missing_nbrh.csv')
nyc_liquor_licenses <- merge(ny_liquor_licenses, zipcodes, by.x = "mod_zip", by.y = "ZIP", all.y = TRUE)

I inspected which zip codes didn’t merge cleanly

nyc_liquor_licenses %>% filter(is.na(City)) %>% select(mod_zip) %>% head

According to the table above, nobody should be able to serve liquor in the following zip codes. Upon inspection, I found an example, Redeye Grill located in zip code 10106. https://www.zomato.com/new-york-city/redeye-grill-midtown/menu This restaurant seems to be serving bloody mary’s and prosecco even though this is not confirmed by the data. This shows that this dataset is incomplete. I did not do a thorough check of all restaurants in New York City, but I would guess that several restaurants serve alcohol even though they aren’t listed in the dataset. For the continuing analysis, I will only focus on the zip codes with clean matches.

nyc_liquor_licenses <- merge(ny_liquor_licenses, zipcodes, by.x = "mod_zip", by.y = "ZIP")

Next, I want to make sure all latitudes and longitudes are included

sum(is.na(nyc_liquor_licenses$Latitude))
[1] 600

This might cause issues in further analysis. However because of the merge, we have an estimate for the latitude and longitude for each zip code. I will impute these missing values using the latitude and longitude from the neighborhood.

nyc_liquor_licenses$Latitude[is.na(nyc_liquor_licenses$Latitude)] <- 
  nyc_liquor_licenses$latitude[is.na(nyc_liquor_licenses$Latitude)]
nyc_liquor_licenses$Longitude[is.na(nyc_liquor_licenses$longitude)] <- 
  nyc_liquor_licenses$longitude[is.na(nyc_liquor_licenses$Longitude)]

Finally, I would like to clean up the dates. They are in string format. Using lubridate, I will convert them to dates and add some columns corresponding to the the year. This will make analysis easier with the other datasets.

nyc_liquor_licenses$License.Original.Issue.Date <- mdy(nyc_liquor_licenses$License.Original.Issue.Date)
nyc_liquor_licenses$License.Effective.Date <- mdy(nyc_liquor_licenses$License.Effective.Date)
nyc_liquor_licenses$License.Expiration.Date <- mdy(nyc_liquor_licenses$License.Expiration.Date)
nyc_liquor_licenses$issue_year <- year(nyc_liquor_licenses$License.Original.Issue.Date)
nyc_liquor_licenses$effective_year <- year(nyc_liquor_licenses$License.Effective.Date)
nyc_liquor_licenses$expiration_year <- year(nyc_liquor_licenses$License.Expiration.Date)

4. Executive Summary

Link to Executive Summary

5. Main Analysis

Zip Code, Neighborhood and Land Value Data

One of our other main questions in this project was looking at how the wealth was distributed amongst each of the zipcodes in the five boroughs. Using the census data from the superzip dataset and mapping that to the zipcode shapefile document we found, we managed to visualize the wealth of each neighborhood. We will later be able to plot the license applications over this mapping of the wealth distribution.

map <- get_map("New York City", source = "google", maptype = "roadmap", zoom = 11, color="bw")
ggmap(map, base_layer = ggplot(aes(x = longitude, y = latitude, color = avg), data = census))  + geom_point(size = 3, alpha=0.7) + scale_color_viridis() + ggtitle("Average Salary of each Zipcode in New York")

This map shows all of the zipcodes and we can clearly see that by far the greatest incomes per zipcode are in the Upper East Side and Upper West Side. There appears to be some grey areaa if you look closely at the Upper West Side. This corresponds to about $150,000/per year on the color map. Then, midtown and downtown also have high incomes. After that, they all fall towards the bottom of the spectrum. Thus, I think it would be useful to make a map showing only Manhattan, and then the everything excluding Manhattan. This should give us a better idea of what areas outside of Manhattan might see more bar/cafe openings.

census_manhattan <- census %>% dplyr::filter(city.x == 'Manhattan')
ggmap(map, base_layer = ggplot(aes(x = longitude, y = latitude, color = avg), data = census_manhattan))  + geom_point(size = 4, alpha=0.7) + scale_color_viridis() + ggtitle("Average Salary of each Zipcode in Manhattan")

This graph shows all of the data filtered for Manhattan only. We can see that once we get to Harlem and above, the average median income per neighborhood drops heavily. The rest of the city is pretty similar.

brooklyn_map2 <- get_map("Brooklyn, NY",  source = "google", zoom = 12, maptype="roadmap", color="bw")
census_brooklyn <- census %>% dplyr:: filter(city.x == 'Brooklyn')
ggmap(brooklyn_map2, base_layer = ggplot(aes(x = longitude, y = latitude, color = avg), data = census_brooklyn))  + geom_point(size = 4, alpha=0.7) + scale_color_viridis() + ggtitle("Average Salary of each Zipcode in Brooklyn")

This shows us the average salary throughout the zipcodes in Brooklyn and we can see that it is highest in general when the neighborhood is closer to Manhattan.

At this point, I had realized that it would be hard to make a combined map illustrating both the income data and licenses data by displaying the zips like this. Thus, I found zipcode shapefile data for NYC and imported it in order to make better maps to be used as a base layer for the licensing data. I also begun labelling the neighborhoods using the labels Marika had created.

plotting_zips1<-read.csv("Data/NY_shapefiles.csv")
ggmap(map) + geom_polygon(aes(fill = avg, x = long, y = lat, group = group), data = plotting_zips1, alpha = 0.8) + ggtitle("Distribution of Average Wealth per Neighborhood in New York") 

Here we see that Manhattan has the highest wealth. It has the wealthiest areas but also some areas in Harlem and more uptown that are less wealthy than most neighborhoods in Queens, Brooklyn and the Bronx.

g_all <- ggmap(map) + geom_polygon(aes(fill = avg, x = long, y = lat, group = group), data = plotting_zips1, alpha = 0.8)
g_all + ggtitle("Distribution of Wealth in New York") + scale_fill_viridis()

Here we see a full distribution using the viridis color scheme. This will be used as the base for the overlaid plots later on and we will then filter by each borough.

Wealth by borough - Adam
plotting_zips_manhattan <- plotting_zips1 %>% filter(city.y == "New York")
plotting_zips_brooklyn <- plotting_zips1 %>% filter(city.y == "Brooklyn")
plotting_zips_bronx <- plotting_zips1 %>% filter(city.y == "Bronx")
plotting_zips_queens <- plotting_zips1 %>% filter(!(city.y %in% c("New York", "Brooklyn", "Bronx", "Staten Island")))
bronx_map <- get_map("Bronx, NY",  source = "google", zoom = 12, maptype="roadmap", color="bw")
brooklyn_map <- get_map("Brooklyn, NY",  source = "google", zoom = 12, maptype="roadmap", color="bw") 
queens_map <- get_map("Astoria, NY",  source = "google", zoom = 12, maptype="roadmap", color="bw") 

We are not using Staten Island since we have no sidewalk cafe data for Staten Island to compare the wealth to.

g_manhattan <- ggmap(map) + geom_polygon(aes(fill = income., x = long, y = lat, group = group), data = plotting_zips_manhattan, alpha = 0.5)
g_manhattan + ggtitle("Distribution of Wealth in Manhattan") + geom_text_repel(data=manhattan_names, aes(mean_lon, mean_lat, label=Neighborhood),fontface="bold", size=3) + scale_fill_viridis()

This map show us the wealth in Manhattan alone by zipcode. We are now using the viridis color scale so that it is perceptually uniform and easier to see differences in color. This clearly shows that the income is very low at Harlem and above, including Morningside Heights. This is likely due to the fact that it is almost exclusively students living in Morningside Heights and Harlem is cheaper. Also, it is interesting to see that Lower East Side has a similar average income to Harlem. The wealthiest areas are by far Upper West Side and Upper East Side. We can also see that we are missing what seems to be data for two zipcodes in the Upper East side. This corresponds to some of the data that Marika had found was missing and still did not appear in the superzip dataset.

g_brooklyn <- ggmap(brooklyn_map) + geom_polygon(aes(fill = income., x = long, y = lat, group = group), data = plotting_zips_brooklyn, alpha = 0.5)
g_brooklyn + ggtitle("Distribution of Wealth in Brooklyn") + geom_text_repel(data=brooklyn_names, aes(mean_lon, mean_lat, label=Neighborhood),fontface="bold", size=3) + scale_fill_viridis()

By observing the income distribution in Brooklyn, we see that in general, the areas closer to Manhattan have the highest income such as Brooklyn Heights and Crown Heights.

g_bronx <- ggmap(bronx_map) + geom_polygon(aes(fill = income., x = long, y = lat, group = group), data = plotting_zips_bronx, alpha = 0.5)
g_bronx + ggtitle("Distribution of Wealth in Bronx") + geom_text_repel(data=bronx_names, aes(mean_lon, mean_lat, label=Neighborhood),fontface="bold", size=3) + scale_fill_viridis()

g_queens <- ggmap(queens_map) + geom_polygon(aes(fill = income., x = long, y = lat, group = group), data = plotting_zips_queens, alpha = 0.5)
g_queens + ggtitle("Distribution of Wealth in Queens") + geom_text_repel(data=queens_names, aes(mean_lon, mean_lat, label=Neighborhood),fontface="bold", size=3) + scale_fill_viridis()

Surprisingly, it seems that in Queens, some of the higher income areas actually appear further away from Manhattan like in Flushing and Forest Hills and Rego Park. However, there are some higher income areas closer to Manhattan as well like East Elmhurst.

Sidewalk Cafe License Data

Any business that operates a portion of a restaurant on a public sidewalk must obtain a Sidewalk Cafe License from New York City. These licenses must be renewed every two years and fall into three categories: enclosed, unenclosed, or small unenclosed sidewalk cafes.

Analyzing by Borough

First, to help better organize the sidewalk cafe licenses by borough, I added a new column called BOROUGH that is set to MANHATTAN, BROOKLYN, BRONX, or QUEENS. I had to manually check that only the cities in Queens had been called out specifically in the CITY column, so it was easy to distinguish them from BRONX or BROOKLYN.

sidewalks <- sidewalks %>% mutate(BOROUGH = ifelse(CITY=="NEW YORK"|CITY=="New York","MANHATTAN",ifelse(CITY=="BROOKLYN","BROOKLYN",ifelse(CITY=="BRONX","BRONX","QUEENS"))))

To get a better understanding of the distribution of these licenses, I have provided a bar graph by borough.

ggplot(sidewalks, aes(x=fct_infreq(BOROUGH)))+geom_bar(aes(fill=BOROUGH))+ggtitle("Frequency of Sidewalk Cafe Licenses by Borough")+xlab("Borough")+ylab("Frequency")+theme_fivethirtyeight()

Clearly Manhattan has the most license requests, followed by Brooklyn, then Queens and finally Bronx. Since at the moment we don’t have neighborhood information (everything in Manhattan is just classified as New York, Brooklyn has only Brooklyn, and Bronx has only the city of Bronx), we can only dive into the Queens data:

queens_cafes <- sidewalks %>% filter(BOROUGH=="QUEENS")
ggplot(queens_cafes, aes(x=fct_infreq(CITY)))+geom_bar(fill="purple")+ggtitle("Frequency of Sidewalk Cafe Licenses in Queens")+xlab("City / Neighborhood")+ylab("Frequency")+coord_flip()+theme_fivethirtyeight()

In Queens, a large percentage of license requests come from Astoria, followed by Long Island City and Forest Hills.

Next, in order to do date comparisons to ascertain which are the new applications vs. renewal applications, I had to convert certain date fields from strings (they were read in as string factors) into dates.

sidewalks$EXPIRATION_DATE<-as.Date(sidewalks$EXPIRATION_DATE, format="%m/%d/%Y")
sidewalks$APP_STATUS_DATE<-as.Date(sidewalks$APP_STATUS_DATE, format="%m/%d/%Y")
sidewalks$SUBMIT_DATE<-as.Date(sidewalks$SUBMIT_DATE, format="%m/%d/%Y")
Licenses by Status

The list of licenses includes active licenses, expired licenses, licenses for businesses that have closed (and are now inactive), licenses which are up for renewal as part of the two year process, or new requests for licenses. To better classify them, I created a new field called STATUS_CLASSIFICATION. Those licenses which are still active and not up for renewal are classified as “ACTIVE”. Those licenses that have been submitted for renewal (either because their expiration date is less than the latest application data, or that an active license is up for review) are classified as “RENEWAL”. Those licenses that are in the sheet but do not have a license number are classified as “NEW”, and the rest are marked as “OLD” to encompass inactive licenses that have not been acted upon.

sidewalks<-sidewalks %>% mutate(STATUS_CLASSIFICATION = ifelse(LIC_STATUS=="Active" & (APP_STATUS=="Application Approved" | APP_STATUS=="Application Review Completed"),"ACTIVE",ifelse(is.na(LICENSE_NBR),"NEW",ifelse((APP_STATUS_DATE>EXPIRATION_DATE | DPQA=="Issued Temp Op Letter") | (LIC_STATUS=="Active" & (APP_STATUS=="Pending Review" | APP_STATUS=="Submitted")),"RENEWAL","OLD"))))

Now that we have classified the status of the licenses, we are able to see how these classifications differ between the boroughs.

ggplot(sidewalks)+geom_mosaic(aes(x=product(STATUS_CLASSIFICATION,BOROUGH),fill=factor(STATUS_CLASSIFICATION)))+coord_flip()+labs(x="Borough",y="License Status", fill="License Designation")+ggtitle("Boroughs by License Status")+theme_fivethirtyeight()

The mosaic plot shows how Bronx and Brooklyn may be getting more new license requests as a percentage of total licenses. Bronx is also getting the highest percentage of renewal requests out of its inactive and active licenses. We can also take a look at the license designations by borough:

ggplot(sidewalks)+geom_mosaic(aes(x=product(BOROUGH,STATUS_CLASSIFICATION),fill=factor(BOROUGH)))+coord_flip()+labs(x="License Status",y="Borough", fill="Borough")+ggtitle("License Status by Borough")+theme_fivethirtyeight()

Looking at the data in this way, you can see how Brooklyn has the second-most new license requests, but how Manhattan still dominates in all license status categories.

Mapping Licenses

We can map the data to have a better view of where the datapoints lie. To get an overall picture, I selected a map centered on Long Island City in Queens so that we can get a good view of both Brooklyn and Bronx in addition to Manhattan.

map <- get_map( location = c(-73.9485424, 40.7454513),  source = "google", zoom = 11, maptype="roadmap", color="bw") 

Plotting each of the restaurants colored by their borough. You can see how Manhattan dominates in the number of sidewalk cafes, and how the sidewalk cafes in Brooklyn and Queens are largely concentrated in the areas closer to Manhattan.

map <- get_map( location = c(-73.9485424, 40.7454513),  source = "google", zoom = 11, maptype="roadmap", color="bw") 

Even with alpha, it is difficult to tell exactly where the largest concentrations of sidewalk cafes lie. Using a density plot, we can better see the concentration of sidewalk cafes around mid-to lower Manhattan, and the clusters in Astoria and Williamsburg.

g <- ggmap(map)+stat_density2d(aes(x=LONGITUDE,y=LATITUDE, fill=..level..),data=sidewalks, geom="polygon", alpha=0.2)
g+scale_fill_gradient(low="yellow",high="red")+ggtitle("Heatmap of Sidewalk Cafe Licenses in NYC")

The next question we can ask is whether there are clear patterns to where new license requests are coming in from, where they are being renewed, or where they have expired without renewal.

ggmap(map)+geom_point(aes(x=LONGITUDE,y=LATITUDE, color=BOROUGH),data=sidewalks, alpha=0.4)+facet_wrap(~STATUS_CLASSIFICATION)+ggtitle("Sidewalk Cafe Licenses by Status")

However, since we are zoomed out a lot and looking at the entire set of licenses, it is difficult to tell the difference between the distributions of license requests. For this analysis, I am only concerned about active licenses (whether they are being renewed or not), and new requests that have not yet been approved. To do this, I created another STATUS_CLASSIFICATION that groups active renewal requests into the Active category, and sets all other non-new requests as “inactive”.

sidewalks <- sidewalks %>% mutate(STATUS_CLASSIFICATION2=ifelse(STATUS_CLASSIFICATION=="ACTIVE" | (STATUS_CLASSIFICATION=="RENEWAL" & LIC_STATUS=="Active"), "ACTIVE", ifelse(STATUS_CLASSIFICATION=="NEW","NEW","OLD")))
new_active <- sidewalks %>% filter(STATUS_CLASSIFICATION2=="ACTIVE" | STATUS_CLASSIFICATION2=="NEW")
ggmap(map)+geom_point(aes(x=LONGITUDE,y=LATITUDE, color=BOROUGH),data=new_active, alpha=0.3)+facet_wrap(~STATUS_CLASSIFICATION2)+ggtitle("Active and New Licenses in New York City")

Since we are quite zoomed out, it is difficult to see what exactly is happening with the New requests. However, if we zoomed in, we would lose information about the Bronx or lower Brooklyn. In order to determine whether there is any useful information there, I have zoomed in on the Bronx region.

Geographic Analysis - Bronx
bronx_map <- get_map("Bronx, NY",  source = "google", zoom = 12, maptype="roadmap", color="bw") 
ggmap(bronx_map)+geom_point(aes(x=LONGITUDE,y=LATITUDE, color=BOROUGH),data=new_active)+facet_wrap(~STATUS_CLASSIFICATION2)+ggtitle("Active and New Licenses in the Bronx")+theme_fivethirtyeight()

Since there are very few active or new licenses in the Bronx, I feel comfortable in zooming in on the rest of Manhattan / Brooklyn and Queens in order to be able to better see what is happening at the expense of Bronx.

Geographic Analysis - Manhattan

I want to take a closer look at what is happening in Manhattan. In order to do this at a more granular level, I will use my merged data with our master_zip document which lists the neighborhoods of each Borough by zip code. I also pulled out the year of the submission of the license application and saved it as SUBMIT_YEAR.

master_zip<-read.csv("Data/zips_master_no_missing_nbrh.csv", strip.white=TRUE)
sidewalks_nbh <- merge(sidewalks, master_zip, by="ZIP", all.x=TRUE)
sidewalks_nbh <- sidewalks_nbh %>% mutate(SUBMIT_YEAR=year(SUBMIT_DATE))
sidewalks_manhattan_active <- sidewalks_nbh %>% filter(BOROUGH=="MANHATTAN" & (STATUS_CLASSIFICATION2=="ACTIVE" | STATUS_CLASSIFICATION2=="NEW"))
manhattan_map <- get_map("Manhattan, NY", source="google", maptype="roadmap", zoom=12, color="bw")

I have also calculated the average locations of the neighborhoods in each borough based on their assigned Zip codes.

ggmap(manhattan_map)+geom_point(aes(x=LONGITUDE, y=LATITUDE, color=nbh), data=sidewalks_manhattan_active)+ggtitle("All active or new sidewalk cafes in Manhattan")

sidewalks_bronx<- sidewalks_nbh %>% filter(BOROUGH=="BRONX")

This view shows all of the distribution of all currently active or new sidewalk cafes in Manhattan. To get a better idea of density, we can also look at a heatmap of this view.

g <- ggmap(manhattan_map)+stat_density2d(aes(x=LONGITUDE,y=LATITUDE, fill=..level..),data=sidewalks_manhattan_active, geom="polygon", alpha=0.3)
g+scale_fill_gradient(low="yellow",high="red")+ggtitle("Heatmap of Sidewalk Cafe Licenses in Manhattan")+geom_text_repel(data=manhattan_names, aes(mean_lon, mean_lat, label=Neighborhood),fontface="bold", size=3)

This heatmap shows the highest concentration of sidewalk cafes in Greenwich Village and NoHo. To see the difference between active and new licenses, we can facet based on our previously calculated categorization.

g <- ggmap(manhattan_map)+stat_density2d(aes(x=LONGITUDE,y=LATITUDE, fill=..level..),data=sidewalks_manhattan_active, geom="polygon", alpha=0.3)+facet_wrap(~STATUS_CLASSIFICATION2)
g+scale_fill_gradient(low="yellow",high="red")+ggtitle("Heatmap of Sidewalk Cafe Licenses in Manhattan")+geom_text_repel(data=manhattan_names, aes(mean_lon, mean_lat, label=Neighborhood),fontface="bold", size=3)+theme_fivethirtyeight()+guides(fill=FALSE)

The heatmap view of the map is not a great one - it is difficult to tell the differences on the same scales in a heatmap. Looking at the same view, but by using geom_point again, we get the following view:

ggmap(manhattan_map)+geom_point(aes(x=LONGITUDE,y=LATITUDE, color=nbh),data=sidewalks_manhattan_active)+facet_wrap(~STATUS_CLASSIFICATION2)+ggtitle("Active and New Licenses in Manhattan") + guides(color=FALSE)+geom_text_repel(data=manhattan_names, aes(mean_lon, mean_lat, label=Neighborhood),fontface="bold", size=3)+theme_fivethirtyeight()

Once again, the data is not telling us too much since there are very few new requests. Perhaps a better way to categorize this data is to look at the years that applications were submitted, therefore ignoring whether a license is currently active or not.

year_bxp<-ggplot(sidewalks_nbh, aes(y=SUBMIT_YEAR,x=1))+geom_boxplot()+ggtitle("Boxplot of Submission Years")+theme_fivethirtyeight()
year_bar<-ggplot(sidewalks_nbh,aes(x=SUBMIT_YEAR))+geom_bar()+ggtitle("Bar Graph of Submission Years")+theme_fivethirtyeight()
grid.arrange(year_bxp, year_bar, ncol=2)

Since there are very few submissions between 2000 and 2014, I will be removing these outliers as potential mistakes where the submit dates were not updated once the licenses were renewed. Now we can look at a mosaic plot of the different neighborhoods and the years that the licenses were requested.

sidewalks_nbh<- sidewalks_nbh %>% filter(SUBMIT_YEAR>2014)
sidewalks_manhattan<- sidewalks_nbh %>% filter(BOROUGH=="MANHATTAN")
manhattan_counts <- sidewalks_manhattan %>% group_by(nbh, SUBMIT_YEAR) %>% summarise(count=n())
manhattan_counts$SUBMIT_YEAR<-as.factor(manhattan_counts$SUBMIT_YEAR)
manhattan_counts$SUBMIT_YEAR<-factor(manhattan_counts$SUBMIT_YEAR, c("2017", "2016","2015"))
ggplot(manhattan_counts, aes(x=reorder(nbh, count), y=count, fill=SUBMIT_YEAR))+geom_bar(stat="identity",position=position_dodge())+coord_flip()+xlab("Neighborhood")+ggtitle("License Requests per Neighborhood and Year")+theme_fivethirtyeight()

It makes sense that 2016 would have more applications than 2015, since many of the 2015 applications have been renewed by now. It looks like Greenwich Village, Upper West Side, and Upper Easy Side consistently have the highest number of requests throughout the three years. However, to get a better understanding of the distribution of areas, we can create three separate heatmaps.

map2015 <- ggmap(manhattan_map)+stat_density2d(aes(x=LONGITUDE,y=LATITUDE, fill=..level..),data=sidewalks_manhattan%>%filter(SUBMIT_YEAR=="2015"), geom="polygon", alpha=0.3)+scale_fill_gradient(low="yellow",high="red")+ggtitle("Manhattan Licenses Applied for in 2015")+geom_text_repel(data=manhattan_names, aes(mean_lon, mean_lat, label=Neighborhood),fontface="bold", size=3)
map2015

map2016<-ggmap(manhattan_map)+stat_density2d(aes(x=LONGITUDE,y=LATITUDE, fill=..level..),data=sidewalks_manhattan%>%filter(SUBMIT_YEAR=="2016"), geom="polygon", alpha=0.3)+scale_fill_gradient(low="yellow",high="red")+ggtitle("Manhattan Licenses Applied for in 2016")+geom_text_repel(data=manhattan_names, aes(mean_lon, mean_lat, label=Neighborhood),fontface="bold", size=3)+guides(fill=FALSE)
map2016

map2017 <- ggmap(manhattan_map)+stat_density2d(aes(x=LONGITUDE,y=LATITUDE, fill=..level..),data=sidewalks_manhattan%>%filter(SUBMIT_YEAR=="2017"), geom="polygon", alpha=0.3)+scale_fill_gradient(low="yellow",high="red")+ggtitle("Manhattan Licenses Applied for in 2017")+geom_text_repel(data=manhattan_names, aes(mean_lon, mean_lat, label=Neighborhood),fontface="bold", size=3)+guides(fill=FALSE)
map2017

Across all three years, you can see how the concentrated area of licenses remains in the Greenwich Village area. However, it looks like the concentration of applicants in the Upper West Side in 2017 has dropped. This map is more useful than the bar chart because it shows the location of the cafes that may be straddling two neighborhoods - information that is not apparent from the grouped bar chart.

We can do the same sort of analysis in all four boroughs that we have sidewalk cafe information about. To make this analysis easier to view, I have created a shiny app which can be found by following this link: ShinyCafe

sidewalks_brooklyn <- sidewalks_nbh %>% filter(BOROUGH=="BROOKLYN")
sidewalks_queens <- sidewalks_nbh %>% filter(BOROUGH=="QUEENS")
sidewalks_bronx <- sidewalks_nbh %>% filter(BOROUGH=="BRONX")
brooklyn_map <- get_map("Brooklyn, NY",  source = "google", zoom = 12, maptype="roadmap", color="bw") 
queens_map <- get_map("Astoria, NY",  source = "google", zoom = 12, maptype="roadmap", color="bw") 

The Shiny app has a brief blurb explaining what each borough can tell us about the movement of sidewalk cafe applications.

Next, I added Adam’s income distribution ploygons to the background of these maps to see what the relationship is to income.

ggmap(manhattan_map)+geom_polygon(aes(fill = income., x = long, y = lat, group = group), data = plotting_zips_manhattan, alpha = 0.7)+geom_point(aes(x=LONGITUDE,y=LATITUDE, color="purple"),data=sidewalks_manhattan, size=1)+ scale_fill_viridis()+geom_text_repel(data=manhattan_names, aes(mean_lon, mean_lat, label=Neighborhood),fontface="bold", size=3)+guides(color=F)

Manhattan has a pretty clear clustering of sidewalk cafes in the higher income areas, with the exception of Lower Mahattan and the Financial District.

ggmap(brooklyn_map)+geom_polygon(aes(fill = income., x = long, y = lat, group = group), data = plotting_zips_brooklyn, alpha = 0.7)+geom_point(aes(x=LONGITUDE,y=LATITUDE, color="purple"),data=sidewalks_brooklyn, size=1)+ scale_fill_viridis()+guides(color=FALSE)

The missing data in Williamsburg is hindering this analysis, but we do see how the average income in Brooklyn Heights, Park Slope, and Red Hook correlate with more sidewalk cafes.

ggmap(queens_map)+geom_polygon(aes(fill = income., x = long, y = lat, group = group), data = plotting_zips_queens, alpha = 0.7)+geom_point(aes(x=LONGITUDE,y=LATITUDE, color="purple"),data=sidewalks_queens, size=1)+ scale_fill_viridis()+geom_text_repel(data=queens_names, aes(mean_lon, mean_lat, label=Neighborhood),fontface="bold", size=3)+guides(color=F)

Astoria and Long Island City are interesting cases with relatively low average incomes but a high concentration of sidewalk cafes. The other area to the south-east, around Parkside and Forest Hills, has a high concentration of cafes and high income.

ggmap(bronx_map)+geom_polygon(aes(fill = income., x = long, y = lat, group = group), data = plotting_zips_bronx, alpha = 0.7)+geom_point(aes(x=LONGITUDE,y=LATITUDE, color="purple"),data=sidewalks_bronx, size=1)+ scale_fill_viridis()+geom_text_repel(data=bronx_names, aes(mean_lon, mean_lat, label=Neighborhood),fontface="bold", size=3)+guides(color=F)

With the very low number of Bronx datapoints, it is not easy to see a realtionship between income and where the sidewalk cafes are located.

mean_incomes <- plotting_zips1 %>% group_by(ZIP) %>% summarise(mean_income=mean(income.))
zip_sidewalk <- sidewalks_nbh %>% group_by(ZIP, BOROUGH) %>% summarise(n_cafes=n())
summary_cafe <- merge(zip_sidewalk, mean_incomes, by="ZIP")
ggplot(summary_cafe, aes(x=mean_income, y=n_cafes))+geom_point()+xlab("Average Income (in Thousands of Dollars)")+ylab("Number of Sidewalk Cafes")+geom_smooth(method=lm)+theme_fivethirtyeight()

cafe.lm = lm(mean_income~n_cafes, data=summary_cafe) 
summary(cafe.lm)$r.squared
[1] 0.1965228

On all the data, there is pretty weak correlation between income and number of sidewalk cafes, with an r-squares of 0.196.

ggplot(summary_cafe, aes(x=mean_income, y=n_cafes))+geom_point()+xlab("Average Income (in Thousands of Dollars)")+ylab("Number of Sidewalk Cafes")+geom_smooth(method=lm)+facet_wrap(~BOROUGH, scales="free")+theme_fivethirtyeight()

Faceting by Borough, we can see how the relationship is actually negative in Bronx and Queens, but positive in Manhattan and Brooklyn (with the strongest relationship in Manhattan).

Putting it All Together

Our main graphs summarizing the data can be seen in the Executive Summary. However, we found that density countour maps of both of the liquor data and sidewalk cafe data tell a powerful story from 2015 to 2017.

liquor<-read.csv("Data/liquor_licenses/massaged_data.csv")
liquor<-liquor %>% filter(Classification=="LIQUOR")
hdr<-c("LONGITUDE","LATITUDE","YEAR","BOROUGH")
side<-sidewalks_nbh %>% filter(SUBMIT_YEAR>2014 & SUBMIT_YEAR<2018)%>%select(LONGITUDE, LATITUDE,SUBMIT_YEAR, BOROUGH)
liq<-liquor %>% filter(effective_year>2014 & effective_year<2018)%>%select(Longitude, Latitude,effective_year, BOROUGH)
colnames(liq)<-hdr
colnames(side)<-hdr
side<-side%>%mutate(TYPE="Sidewalk Cafe")
liq <-liq %>% mutate(TYPE="Bar")
total <- rbind(liq,side)
ggmap(map)+stat_density2d(aes(x=LONGITUDE,y=LATITUDE, color=TYPE),size=2, alpha=.5,contour=TRUE,data=total, geom="density2d")+xlab("") + ylab("")+theme(axis.text.x=element_blank(),axis.text.y=element_blank(),axis.ticks.x=element_blank(),axis.ticks.y=element_blank(),axis.line = element_line(color = NA))+ guides(fill=FALSE,alpha=FALSE)+facet_wrap(~YEAR)+theme_fivethirtyeight()

The graph shows how in 2015, most establishments are concentrated mostly in Manhattan, with bubbles in Brooklyn Heights, Williamsburg, and Astoria. However, in 2016, you can see the bars density contours spread out to neighborhoods next to Manhattan, especially in Brooklyn. In 2017, we are seeing the same happen with sidewalk cafes.

Liquor License Data

First I checked the distribution of liquor licenses by zip code.

temp <- nyc_liquor_licenses %>% group_by(mod_zip) %>% summarise(num = n())
qplot(temp$num, binwidth = 10)+theme_fivethirtyeight()

It is not surprising that this is a long tailed distribution, however it is very surprising that some zip codes have very high concentrations of liquor licenses. I looked into the zip codes with especially high number of licenses.

nyc_liquor_licenses %>% group_by(mod_zip, nbh) %>% summarise(num = n()) %>% filter(num > 400)

Unsurprisingly, 3 of these 5 highly concentrated areas are in Midtown. One interesting thing I found while investigating these zip codes were the singular premises with the highest concentrations.

nyc_liquor_licenses %>% group_by(Actual.Address.of.Premises..Address1.) %>% summarise(num = n()) %>% filter(num>15)

Both of these are transportation hubs. As a frequent commuter from Grand Central, I seemed very surprised that there were 101 places there that sell alcohol.

nyc_liquor_licenses %>% filter(Actual.Address.of.Premises..Address1. == "GRAND CENTRAL TERMINAL") %>% group_by(Premises.Name) %>% summarise(num = n())

This was even more surprising to me as I have never been able to purchase alcohol on Metro North. Upon further inspection

nyc_liquor_licenses %>% filter(Actual.Address.of.Premises..Address1. == "GRAND CENTRAL TERMINAL") %>%
  filter(Premises.Name == 'METRO NORTH COMMUTER RAILROAD') %>% group_by(Agency.Zone.Office.Name) %>% 
  summarise(num = n())

Albany issues all the licenses. I have never been on a train to Albany, but next time I am, I will definitely ask for an adult beverage.

This made me want to know more about the different types of liquor licenses that are granted.

nyc_liquor_licenses %>%  ggplot(., aes(x=fct_infreq(License.Type.Name))) + geom_bar() + coord_flip() +theme_fivethirtyeight()

There are a lot of fringe categories such as Distiller B-1 and Distiller A that are offered to a few places. However, the active liquor licenses are unsurprisingly dominated by on-premise liquor establishments, sometimes known as bars. There is also a distinction between beer and wine. I would like to explore that further.

Another basic variable I wanted to investigate was the how effective liquor licences have grown over the years.

nyc_liquor_licenses %>% group_by(effective_year) %>% summarise(num = n()) %>% 
  ggplot(., aes(x=effective_year, y = num)) + geom_bar(stat='identity')+theme_fivethirtyeight()

The biggest years represented are 2015 and 2016. I am not surprised by the fact that 2017 has fewer records. The year is only 1/3 done, and I don’t know how quickly it takes an active liquor license to be successfully recorded so it might be possible that liquor licenses that have been granted in Februrary and March are not included in the dataset. I do want to look a little further at 2014.

My first hypothesis is that only licenses in later months were included

nyc_liquor_licenses %>% mutate(month = months(License.Effective.Date)) %>% 
  mutate(month_ind = month(License.Effective.Date)) %>% group_by(month, month_ind, effective_year) %>% 
  summarise(num = n()) %>% ggplot(., aes(x = reorder(month, -month_ind), y = num)) + geom_bar(stat='identity') +
  facet_wrap(~effective_year) + coord_flip()+theme_fivethirtyeight()

Although this data would be better shown in a time-series plot, the pattern is clear. The distributon of bars in 2014 by month does not suggest that my hypothesis is correct as the number of active licenses from March to December in that year is roughly constant and all other years other than 2017 show that licenses that are effective in January and February are fewer in comparison to other months. It might be coincidental, but in 2014-2016, March and October have a slightly higher number of licenses that become effective that month.

I then decided to look into license type.

nyc_liquor_licenses %>%  filter(effective_year == 2014) %>% ggplot(., aes(x=fct_infreq(License.Type.Name))) + geom_bar() + coord_flip()+theme_fivethirtyeight()

Astonishingly, there are way fewer types of licenses in 2014. Additionally, there are almost no on-premise liquor licenses. My guess as to why this happens is that the liquor licenses for bars expire quicker. I wanted to look into how long licenses are effective for.

temp <- nyc_liquor_licenses %>% mutate(active_length = License.Expiration.Date - License.Effective.Date)
qplot(temp$active_length, binwidth = 7)+theme_fivethirtyeight()

Essentially, there seem to be two huge spikes which seem to correspond to 2 and 3 years

nyc_liquor_licenses %>% mutate(active_length = License.Expiration.Date - License.Effective.Date) %>% 
  group_by(active_length) %>% summarise(num = n()) %>% arrange(desc(num)) %>% head

By far, the most common lengths of licenses are 730 and 1095 which are 2 and 3 years respectively. Looking at the graph, the rest of the lengths are close to these lengths

nyc_liquor_licenses %>% filter(License.Type.Name == 'ON-PREMISES LIQUOR') %>% 
  mutate(active_length = License.Expiration.Date - License.Effective.Date) %>% 
  qplot(x = active_length, data = ., binwidth = 7)+theme_fivethirtyeight()

It does look like on-premise liquor licenses are for two years.

nyc_liquor_licenses %>% filter(License.Type.Name == 'GROCERY BEER, WINE PROD') %>% 
  mutate(active_length = License.Expiration.Date - License.Effective.Date) %>% 
  qplot(x = active_length, data = ., binwidth = 7)+theme_fivethirtyeight()

In contrast, grocery licenses look to be for around 3 years

The month plot made me question how licenses become active during the month.

nyc_liquor_licenses %>% mutate(dom  = day(License.Effective.Date)) %>% qplot(x=dom, data=.)+theme_fivethirtyeight()

And for expiring date,

nyc_liquor_licenses %>% mutate(dom  = day(License.Expiration.Date)) %>% qplot(x=dom, data=.)+theme_fivethirtyeight()

From these two plots, we can see that licenses seem to become effective at the beginning of the month and expire at the end of the month,

I also wanted to look at the spatial distribution.

ggmap(manhattan_map)+stat_density2d(aes(x=Longitude,y=Latitude, fill=..level..),data=nyc_liquor_licenses%>%filter(effective_year=="2015") %>% 
                                      filter(city.x == "Manhattan"), geom="polygon", alpha=0.3)+scale_fill_gradient(low="yellow",high="red")

ggmap(manhattan_map)+stat_density2d(aes(x=Longitude,y=Latitude, fill=..level..),data=nyc_liquor_licenses%>%filter(effective_year=="2016") %>% 
                                      filter(city.x == "Manhattan"), geom="polygon", alpha=0.3)+scale_fill_gradient(low="yellow",high="red")

In these two maps we see the spatial distribution between two years in which the license became effective. They are both very similar with the Midtown and Lower East Side having the highest density. The biggest difference between the two is that Morningside heights and north has more activity in 2016. Again, this isn’t to say that new places popped up in these neighborhoods, but licensed became active in this year.

ggmap(manhattan_map)+stat_density2d(aes(x=Longitude,y=Latitude, fill=..level..),
                                    data=nyc_liquor_licenses%>% filter(city.x == "Manhattan") %>% 
                                      filter(License.Type.Name == "EATING PLACE BEER"), geom="polygon", alpha=0.3)+scale_fill_gradient(low="yellow",high="red")

ggmap(manhattan_map)+stat_density2d(aes(x=Longitude,y=Latitude, fill=..level..),
                                    data=nyc_liquor_licenses%>% filter(city.x == "Manhattan") %>% 
                                      filter(License.Type.Name == "RESTAURANT WINE"), geom="polygon", alpha=0.3)+scale_fill_gradient(low="yellow",high="red")

These two maps compare wine in restaurants to beer in eating places. In the top map, the hotspot for beer stretches from Midtown all the way down to the Lower East Side. Additionally, outside of the upper east side and upper west side, the map is pretty much covered. In contrast, for wine, the concentration is clearly concentrated in the South and the East. Furthermore, the areas not covered in the beer map are covered in the wine map outside of Central Park. These areas tend to be more affluent, so this gives some evidence that wine is enjoyed more by more affluent people. ## 6. Conclusion
This project delved into exploring where sidewalk cafe and liquor licenses are issued and the geospatial relationship between these variables and income. Intuitively, areas generally associated as up and coming neighborhoods such as WIlliamsburg were found to see increases in the past two years. However, in Queens, there was found to be a negative correlation between income and sidewalk cafe and liquor licenses.

All of these conclusions should be taken with a grain of salt. First, we can’t be sure that the data for either sidewalk cafes or liquor licenses is complete. There might be places who operate without a license or places that aren’t recorded properly in the dataset. One such example was found in the liquor license data. Additionally, only two liquor licenses were granted to non rail-cars in Grand Central. This seems to be underestimating the true value. There were also two missing zip codes in the data and 15 zip codes that were not found in the liquor license data, giving further evidence that this dataset was incomplete.

The income data was collected by the census. While this data should be high quality, the most recent census was conducted in 2010 which means data might not be recent, especially considering the analysis focused on the years 2015-2016. Also, we used when the license was issued as a proxy for which neighborhoods were growing. In the sidewalk cafe data, renewed licenses overwrote the existing license for the place. In the liquor license data, it was seen that licenses had to renewed every two years for bars. Without access to all the historical data it is hard to separate growth from the renewal of licenses.

In terms of correlation, some neighborhoods such as MIdtown were found to have a high number of liquor licenses and sidewalk cafes. These correlations might be spurious as more people tend to live in these areas. This analysis only focused on how income affected sidewalk cafes and bars popping up, but the real cause might be something we didn’t account for such as demographic information, ease of transportation, etc.

For an introductory study, we were able to identify several trends in how licenses are issued, how wealth is distributed geographically across New York City, and correlations between these two variables. Without doing any modelling, we were able to generate and assess the validity of several hypotheses, and some of our preconceived notions were proven wrong. Hopefully, this study was able to show promise in how visualizing and mapping licenses can help provide insight about the what drives certain institutions to grow.

LS0tDQp0aXRsZTogIkVEQVYgRmluYWwgUHJvamVjdCAtIE5ZQyBMaWNlbnNlIGFuZCBJbmNvbWUgRGF0YSINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogZGVmYXVsdA0KICBodG1sX25vdGVib29rOg0KICAgIHRoZW1lOiBqb3VybmFsDQotLS0NCg0KIyMgMS4gSW50cm9kdWN0aW9uDQoNCk9uZSBvZiBvdXIgdGVhbW1hdGVzIGxpdmVzIGluIEhhbWlsdG9uIEhlaWdodHMsIGluIGFuICd1cCBhbmQgY29taW5nJyBhcmVhIGNhbGxlZCBTdWdhciBIaWxsIHdoaWNoIGhhcyBzZWVuIGEgbG90IG9mIG5ldyBiYXJzIGFuZCByZXN0YXVyYW50cyBvcGVuIHVwICh3ZSBoaWdobHkgc3VnZ2VzdCBjaGVja2luZyBvdXQgSGFybGVtIFB1YmxpYykuIFdlIHdlcmUgd29uZGVyaW5nIGhvdyBvbmUgY291bGQgZWFzaWx5IGlkZW50aWZ5IGFuICd1cCBhbmQgY29taW5nJyBhcmVhIGJlZm9yZSBhbGwgdGhlIGxhbmQgdmFsdWVzIHJpc2UuIENvdWxkIHRoZXkgYmUgaWRlbnRpZmllZCBieSBuZXcgd2luZWJhcnMgYW5kIHB1YnM/IE9yIHBlcmhhcHMgYnkgc2lkZXdhbGsgY2FmZXMgdGhhdCBwb3AgdXAgYXMgc29vbiBhcyB0aGUgd2VhdGhlciBnZXRzIHdhcm1lcj8gIA0KDQpXZSBmb3VuZCBhIGRhdGFzZXQgZm9yIGxpcXVvciBsaWNlbnNlcyBpbiBOZXcgWW9yayBTdGF0ZSBhdCBkYXRhLm55Lmdvdi4gQ2FsbGVkIFsiTGlxdW9yIEF1dGhvcml0eSBRdWFydGVybHkgTGlzdCBvZiBBY3RpdmUgTGljZW5zZXMiXShodHRwczovL2RhdGEubnkuZ292L0Vjb25vbWljLURldmVsb3BtZW50L0xpcXVvci1BdXRob3JpdHktUXVhcnRlcmx5LUxpc3Qtb2YtQWN0aXZlLUxpY2Vuc2VzL2hydnMtZnhzMiksIHRoaXMgZGF0YXNldCBnaXZlcyBpbmZvcm1hdGlvbiBhYm91dCBhbGwgb2YgdGhlIGN1cnJlbnRseSBhY3RpdmUgbGlxdW9yIGxpY2Vuc2VzIGFzIG9mIEFwcmlsLCAyMDE3Lg0KDQpXZSBhbHNvIGZvdW5kIGEgZGF0YXNldCBhYm91dCBhbGwgcmVxdWVzdGVkIGFuZCBhY3RpdmUgc2lkZXdhbGsgY2FmZSBsaWNlbnNlIGFwcGxpY2F0aW9ucyBpbiBOZXcgWW9yayBDaXR5IChhbmQgdGhlIHN1cnJvdW5kaW5nIGJvcm91Z2hzIGV4Y2x1ZGluZyBMb25nIElzbGFuZCkgY2FsbGVkIFsiU2lkZXdhbGsgQ2FmPyBMaWNlbnNlcyBhbmQgQXBwbGljYXRpb25zIl0oaHR0cHM6Ly9kYXRhLmNpdHlvZm5ld3lvcmsudXMvQnVzaW5lc3MvU2lkZXdhbGstQ2FmLUxpY2Vuc2VzLWFuZC1BcHBsaWNhdGlvbnMvcWNkai1yd2h1KS4gVGhpcyBoYXMgYWxzbyBiZWVuIHVwZGF0ZWQgaW4gQXByaWwsIDIwMTcuDQoNCkZpbmFsbHksIHdlIHdhbnRlZCB0byBrbm93IHdoZXRoZXIgdGhlcmUgaXMgYSBjbGVhciBjb3JyZWxhdGlvbiBiZXR3ZWVuIHRoZSBhdmVyYWdlIGluY29tZSBvZiBuZWlnaGJvcmhvb2QgaW5oYWJpdGFudHMgYW5kIHRoZSBudW1iZXIgb2Ygc2lkZXdhbGsgY2FmZXMgYW5kIGJhcnMgaW4gdGhhdCBhcmVhLiBUbyBkbyB0aGlzLCB3ZSB1c2VkIHRoZSBmb2xsb3dpbmcgZGF0YXNldHM6ICAgDQoNCiogW0luY29tZSBUaWVkIHRvIFppcGNvZGVdKGh0dHBzOi8vZ2l0aHViLmNvbS9qY2hlbmc1L3N1cGVyemlwL3RyZWUvbWFzdGVyL2RhdGEpICANCg0KKiBbTWFwcGluZyBvZiBaaXBDb2RlcyB0byBOZWlnaGJvcmhvb2RzIGFuZCBMb24vTGF0IGRhdGFdKGh0dHBzOi8vd3d3LmJhcnVjaC5jdW55LmVkdS9jb25mbHVlbmNlL2Rpc3BsYXkvZ2VvcG9ydGFsL05ZQytHZW9ncmFwaGllcykgIA0KDQoqIFtaaXBjb2RlIFNoYXBlZmlsZXNdKGh0dHBzOi8vZGF0YS5jaXR5b2ZuZXd5b3JrLnVzL0J1c2luZXNzL1ppcC1Db2RlLUJvdW5kYXJpZXMvaThpdy14ZjR1KSAgDQoNCg0KV2UgcmVhbGl6ZWQgdGhhdCB0aGVyZSBpcyBubyBhdXRob3JpdGF0aXZlIGRhdGFzb3VyY2UgbWFwcGluZyBlYWNoIHppcCBjb2RlIHRvIGEgc2luZ2xlIG5laWdoYm9yaG9vZC4gSW4gb3JkZXIgdG8gYmUgYWJsZSB0byBtYXAgbmVpZ2hib3Job29kIGxvY2F0aW9ucyBvbiBhIG1hcCwgd2Ugd3JvdGUgYSBweXRob24gc2NyYXBlciB0byBwdWxsIHppcGNvZGUgdG8gbmVpZ2hib3Job29kIGRhdGEgZnJvbSB0aGUgZm9sbG93aW5nIGxvY2F0aW9ucywgYW5kIHRoZW4gam9pbmVkIGl0IHdpdGggdGhlIHppcGNvZGUgdG8gbG9jYXRpb24gZGF0YTogIA0KDQoqIFtNYW5oYXR0YW4gTmVpZ2hib3Job29kc10oaHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS9tYXBzL2QvdS8wL3ZpZXdlcj9taWQ9MVA2Q2hkeVpkRGtDMk4zWDRiaUVFMHlnNWQ5MCZobD1lbl9VUyZsbD00MC43ODY5MTQ3ODk3MTI4MiUyQy03My45NjU2Mzg0ODQ2MTkxNSZ6PTEyKSAgDQoNCiogW0Jyb29rbHluIE5laWdoYm9yaG9vZHNdKGh0dHA6Ly93d3cuYnJvb2tseW4uY29tL3ppcGNvZGVzLnBocCkgIA0KIA0KKiBbUXVlZW5zIE5laWdoYm9yaG9vZHNdKGh0dHA6Ly9xdWVlbnMuYWJvdXQuY29tL29kL25laWdoYm9yaG9vZHMvYS96aXAtY29kZXMtcXVlZW5zLW55Lmh0bSkgIA0KDQoqIFtCcm9ueCBOZWlnaGJvcmhvb2RzXShodHRwOi8vc3RhdGlzdGljYWxhdGxhcy5jb20vY291bnR5LXN1YmRpdmlzaW9uL05ldy1Zb3JrL0Jyb254LUNvdW50eS9Ccm9ueC9PdmVydmlldykNCg0KIyMgMi4gVGVhbQ0KDQoxLiBBZGFtIENvdmllbnNreSAtIGluIGNoYXJnZSBvZiB0aGUgaW5jb21lIHBlciB6aXBjb2RlIGRhdGENCg0KKiBGb3VuZCBhbmQgYW5hbHl6ZWQgdGhlIG9yaWdpbmFsIHByb3BlcnR5IHZhbHVlIGRhdGFzZXQgd2hpY2ggd2UgZGlkIG5vdCBlbmQgdXAgdXNpbmcgb25jZSB3ZSBkZXRlcm1pbmVkIHRoYXQgaXQgd291bGQgYmUgaGFyZCB0byBtYXAgdGhlIG5laWdoYm9yaG9vZHMgYW5kIGNhZmUgYW5kIGxpcXVvciBsaWNlbnNlIGRhdGEgd2l0aG91dCB6aXBjb2Rlcy4gDQoNCiogRm91bmQgYW5kIGNsZWFuZWQgbmV3IHN1cGVyemlwIGRhdGFzZXQgDQoNCiogSm9pbmVkIHRoZSB6aXBjb2RlcyB0byBpbml0aWFsIHNldCBvZiBuZWlnaGJvcmhvb2RzDQoNCiogQ3JlYXRlZCB6aXBjb2RlIHNoYXBlZmlsZSBncmFwaHMgYnkgaW5jb21lIA0KDQoqIFdyb3RlIHRoZSBkYXRhIHF1YWxpdHkgYW5hbHlzaXMgYW5kIGV4cGxvcmF0aW9uIGNvcnJlc3BvbmRpbmcgdG8gaGlzIHNlY3Rpb25zDQoNCiogRGV2ZWxvcGVkIFNoaW55IGNvbXBvbmVudCBvZiB0aGUgRXhlY3V0aXZlIFN1bW1hcnkNCg0KDQoyLiBSb2hhbiBQaXRyZSAtDQoNCiogQW5hbHlzZWQgYW5kIGNsZWFuZWQgTGlxdW9yIExpY2Vuc2UgZGF0YXNldA0KDQoqIFdyb3RlIHRoZSBkYXRhIHF1YWxpdHkgYW5hbHlzaXMgYW5kIGV4cGxvcmF0aW9uIGNvcnJlc3BvbmRpbmcgdG8gaGlzIHNlY3Rpb25zDQoNCiogV3JvdGUgQ29uY2x1c2lvbg0KDQozLiBNYXJpa2EgTG9obXVzIC0NCg0KKiBXcm90ZSBTdW1tYXJ5IA0KDQoqIElkZW50aWZpZWQgYW5kIGZpbGxlZCBpbiBtaXNzaW5nIHppcGNvZGVzIGFuZCBuZWlnaGJvcmhvb2RzIGluIHRoZSBtYXN0ZXIgemlwIGZpbGUNCg0KKiBDb2RlZCB3ZWItc2NyYXBlcnMgdG8gZmlsbCBpbiB6aXBjb2RlIHRvIG5laWdoYm9yaG9vZCBtYXBwaW5ncw0KDQoqIEdlbmVyYXRlZCBiYXNlIHRlbXBsYXRlIG1hcHMgZm9yIHRoZSBwcm9qZWN0IHRvIHdvcmsgb24NCg0KKiBTZXQgdXAgYW5kIHVwbG9hZGVkIFNoaW55IGFwcHMgdG8gc2VydmVyDQoNCiogQW5hbHlzZWQgYW5kIGNsZWFuZWQgU2lkZXdhbGsgTGljZW5zZSBkYXRhc2V0DQoNCiogV3JvdGUgdGhlIGRhdGEgcXVhbGl0eSBhbmFseXNpcyBhbmQgZXhwbG9yYXRpb24gY29ycmVzcG9uZGluZyB0byBoZXIgc2VjdGlvbnMNCg0KKiBXcm90ZSBFeGVjdXRpdmUgU3VtbWFyeQ0KDQoNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCg0KcmVxdWlyZShnZ3Bsb3QyKQ0KcmVxdWlyZShkcGx5cikNCnJlcXVpcmUodGlkeXIpDQpyZXF1aXJlKGV4dHJhY2F0KQ0KcmVxdWlyZShmb3JjYXRzKQ0KcmVxdWlyZShnZ21vc2FpYykNCnJlcXVpcmUoZ2dtYXApDQpyZXF1aXJlKGdyaWRFeHRyYSkNCnJlcXVpcmUoZ2dyZXBlbCkNCnJlcXVpcmUobHVicmlkYXRlKQ0KcmVxdWlyZShzaGlueSkNCnJlcXVpcmUoemlwY29kZSkNCnJlcXVpcmUodmlyaWRpcykNCnJlcXVpcmUocmdkYWwpDQpyZXF1aXJlKG1hcHRvb2xzKQ0KcmVxdWlyZShnZ3RoZW1lcykNCmBgYA0KDQojIyAzLiBBbmFseXNpcyBvZiBEYXRhIFF1YWxpdHkNCg0KIyMjIyBaaXAgQ29kZSwgTmVpZ2hib3Job29kIGFuZCBMYW5kIFZhbHVlIERhdGENCg0KV2UgcmVhbGl6ZWQgdGhhdCB0aGUgb3JpZ2luYWwgZGF0YSB3YXMgbm90IGdvaW5nIHRvIGJlIHN1ZmZpY2llbnQuIFdlIG9ubHkgaGFkIGEgZmV3IHJlbnRhbCBvYnNlcnZhdGlvbnMgZnJvbSBTdGF0ZW4gSXNsYW5kLCBRdWVlbnMgYW5kIHRoZSBCcm9ueCwgYW5kIGFsbCBvZiB0aGUgb2JzZXJ2YXRpb25zIHdlcmUgZGlyZWN0bHkgYm9yZGVyaW5nIE1hbmhhdHRhbi4gV2UgZGVjaWRlZCB0byBmaW5kIG5ldyBkYXRhLiBXZSBmb3VuZCB0aGUgZGF0YSBmb3IgYSBTaGlueSBwcm9qZWN0IHRpdGxlZCBTdXBlcnppcCB3aXRoIEluY29tZSBwZXIgemlwY29kZSBmb3IgdGhlIGVudGlyZSBVbml0ZWQgU3RhdGVzLiBTaW5jZSB3ZSBhbHNvIHdhbnRlZCB0aGUgaW5jb21lIHBlciBuZWlnaGJvcmhvb2QsIHdlIGZvdW5kIGEgbWFwcGluZyBmcm9tIHppcGNvZGUgdG8gbmVpZ2hib3Job29kIG5hbWUgdGhhdCB3ZSB3aWxsIHVzZSBmb3Igb3VyIGZpbmFsIG9ic2VydmF0aW9ucy4gSXQgcHJvdmVkIHJhdGhlciBkaWZmaWN1bHQgdG8gYWN0dWFsbHkgY2xlYW4gdGhpcyBtYXBwaW5nLg0KDQpgYGB7cn0NCnN1cGVyemlwIDwtIHJlYWQudGFibGUoJ0RhdGEvc3VwZXJ6aXAuY3N2JywgaGVhZGVyID0gVFJVRSkNCm5laWdoYm9yaG9vZCA8LSByZWFkLmNzdignRGF0YS9ueWNfemN0YS5jc3YnKQ0KY29sbmFtZXMobmVpZ2hib3Job29kKVsxXSA8LSAiemlwY29kZSINCm5laWdoYm9yaG9vZDIgPC0gbmVpZ2hib3Job29kWyxjKDEsIDYpXQ0KbmVpZ2hib3Job29kMiR6aXBjb2RlIDwtIGFzLmZhY3RvcihuZWlnaGJvcmhvb2QyJHppcGNvZGUpDQpgYGANCg0KDQpgYGB7cn0NCnN1cGVyemlwMiA8LSBzdXBlcnppcCAlPiUgZHBseXI6OmZpbHRlcihzdGF0ZSA9PSAiTlkiKQ0Kc3VwZXJ6aXAzIDwtIHN1cGVyemlwMiAlPiUgZmlsdGVyKGNpdHkgJWluJSBjKCJCcm9va2x5biIsICJRdWVlbnMgVmlsbGFnZSIsICJTdGF0ZW4gSXNsYW5kIiwgIk5ldyBZb3JrIiwgIkJyb254IikpDQpzdXBlcnppcDMkY2l0eVtzdXBlcnppcDMkY2l0eT09Ik5ldyBZb3JrIl0gPSAiTWFuaGF0dGFuIg0KYGBgDQoNCldpdGhvdXQgZmlsdGVyaW5nIGZvciB0aGUgc3RhdGUgPT0gTlksIHdlIGZvdW5kIHdlIGhhZCBzb21lIGV4dHJhIHJvd3MuIFdoZW4gd2UgbG9va2VkIHVwIHRoZXNlIHppcGNvZGVzIG9ubGluZSwgd2UgZm91bmQgdGhhdCB0aGVyZSB3ZXJlIDYgb3RoZXIgQnJvb2tseW5zIGluIHRoZSBVbml0ZWQgU3RhdGVzLiBUaHVzLCB3ZSBoYWQgdG8gZmlyc3QgZmlsdGVyIGZvciBOWSwgdGhlbiBvbmx5IGNvbnRhaW5pbmcgdGhlIGJvcm91Z2hzLiBUaGVuLCB3ZSBjaGFuZ2VkIHRoZSBib3JvdWdoIGZyb20gTmV3IFlvcmsgdG8gYmUgTWFuaGF0dGFuDQoNCg0KYGBge3J9DQptYXN0ZXJfemlwIDwtIGZ1bGxfam9pbihzdXBlcnppcDMsIG5laWdoYm9yaG9vZDIsIGJ5ID0gJ3ppcGNvZGUnKQ0KY29sbmFtZXMobWFzdGVyX3ppcClbMTFdIDwtICJhcmVhIg0KYGBgDQoNClRoaXMgc2hvd2VkIG1lIHRoZSByb3dzIHdoaWNoIGhhdmUgTkFzLiBBcyB3ZSBjYW4gY2xlYXJseSBzZWUsIHRoZXJlIHdlcmUgbW9yZSB6aXBjb2RlcyBpbiB0aGUgbmVpZ2hib3Job29kIGRhdGEgdGhhbiBpbiB0aGUgc3VwZXJ6aXAgZGF0YS4gT3VyIHdheSBvZiBhZGp1c3RpbmcgZm9yIHRoaXMgd2lsbCBiZSB0byBkZXRlcm1pbmUgYW4gYXZlcmFnZSBpbmNvbWUgZm9yIGVhY2ggbmVpZ2hib3Job29kLiBUaGVuLCB3ZSB3aWxsIGJlIGltcHV0aW5nIHRoZSBtaXNzaW5nIGluY29tZXMgdXNpbmcgdGhlIGF2ZXJhZ2UgaW5jb21lcyBmb3IgdGhlIHppcGNvZGVzIHdoaWNoIHdlIGRvIGhhdmUgaW4gdGhhdCBuZWlnaGJvcmhvb2QuDQoNCg0KYGBge3J9DQpBVkdfSU5DT01FX05FSUdIQk9SSE9PRCA8LSBtYXN0ZXJfemlwICU+JSBncm91cF9ieShhcmVhKSAlPiUgc3VtbWFyaXNlKGF2ZyA9IG1lYW4oaW5jb21lLiwgbmEucm09VFJVRSkpDQpgYGANCg0KVXBvbiB2aWV3aW5nIHRoZSBvdXRwdXQsIHdlIHN0aWxsIGhhdmUgbWFueSBOQXMgaW4gdGhlIGF2ZyBjb2x1bW4sIHdoZW4gbG9va2luZyBhdCB0aGUgbWFzdGVyX3ppcCBkYXRhZnJhbWUgZmlsdGVyZWQgZm9yICJBc3RvcmlhICYgTG9uZyBJc2xhbmQgQ2l0eSIsIHdoaWNoIGlzIHRoZSBmaXJzdCBuZWlnaGJvcmhvb2Qgd2l0aCBhbiBhdmcgb2YgTkEsIHdlIHNlZSB0aGF0IGl0J3MgYmVjYXVzZSB3ZSBoYXZlIG5vIGluY29tZSBkYXRhIGZvciBhbnkgemlwY29kZXMgaW4gdGhpcyBhcmVhLiBUaGVyZWZvcmUsIG9uY2Ugd2Ugam9pbiB0aGUgYXZlcmFnZSBpbmNvbWUgd2l0aCB0aGUgbWFzdGVyX3ppcCBkYXRhZnJhbWUgd2Ugd2lsbCBkcm9wIHRoZXNlIHJvd3Mgd2l0aCBOQXMuDQoNCmBgYHtyfQ0KbWFzdGVyX3ppcDIgPC0gbGVmdF9qb2luKG1hc3Rlcl96aXAsIEFWR19JTkNPTUVfTkVJR0hCT1JIT09ELCBieSA9ICJhcmVhIikNCmBgYA0KDQpIZXJlIHdlIGhhdmUgam9pbmVkIHRoZSBhdmVyYWdlIGluY29tZSBwZXIgbmVpZ2hib3Job29kIGRhdGEgYmFjayB0byB0aGUgcmVzdCBvZiB0aGUgaW5jb21lIGRhdGEuIA0KDQpgYGB7cn0NCmRhdGEoemlwY29kZSkNCmNlbnN1cyA8LSBtZXJnZShtYXN0ZXJfemlwMiwgemlwY29kZSwgYnkueCA9ICd6aXBjb2RlJywgYnkueSA9ICd6aXAnKQ0KY2Vuc3VzIDwtIGNlbnN1cyAlPiUgZmlsdGVyKCFpcy5uYShhdmcpKQ0KYGBgDQoNCkhlcmUgd2UgYXJlIHVzaW5nIHRoZSBwYWNrYWdlIHppcGNvZGUgdG8gbWFwIGFsbCBvZiBvdXIgemlwY29kZXMgdG8gZ2Vvc3BhdGlhbCBjb29yZGluYXRlcyBzbyB0aGF0IHdlIGNhbiBwbG90IHRoZW0gdXNpbmcgZ2dtYXAuIFRoZW4sIHdlIGFyZSBmaWx0ZXJpbmcgb3V0IGFsbCBvZiB0aGUgemlwY29kZXMgYW5kIG5laWdoYm9yaG9vZHMgZm9yIHdoaWNoIHdlIGhhdmUgbm8gaW5jb21lIGRhdGEgYXQgYWxsIHdpdGhpbiB0aGF0IG5laWdoYm9yaG9vZC4NCg0KYGBge3J9DQptaXNzaW5nX3ppcHMgPC0gYygxMDA2NSwgMTAwNzUsIDEwMTA2LCAxMDI4MSwgMTExMDEsIDExMTAyLCAxMTEwMywgMTExMDQsIDExMTA1LCAxMTEwNiwgMTExMDksIDExMjQ5LCAxMTM2NiwgMTEzNjcsIDExMzY4LCAxMTM3MCwgMTEzNzIsIDExMzczLCAxMTM3NCwgMTEzNzUsIDExMzc3LCAxMTM3OSwgMTE0MzUsIDExNjk0KQ0KZmlsdGVyZWRfbWlzc2luZ196aXBzIDwtIGZpbHRlcihzdXBlcnppcCwgemlwY29kZSAlaW4lIG1pc3NpbmdfemlwcykNCnNldGRpZmYobWlzc2luZ196aXBzLCBmaWx0ZXJlZF9taXNzaW5nX3ppcHMkemlwY29kZSkNCmBgYA0KDQpIZXJlLCBNYXJpa2EgaGFkIGluZm9ybWVkIG1lIHRoYXQgd2UgaGFkIG1pc3NpbmcgemlwY29kZXMgaW4gdGhlIGZpbmFsIGNlbnN1cyBkYXRhZnJhbWUuIFRoZXJlZm9yZSwgSSB3ZW50IGJhY2sgdG8gdGhlIHN1cGVyemlwIGRhdGFzZXQgYW5kIGZvdW5kIHRoYXQgMTggb2YgdGhlIDI0IHppcGNvZGVzIGFwcGVhcmVkIHRoZXJlLiBIb3dldmVyLCB0aGV5IHdlcmUgbGFiZWxlZCBhcyBiZWluZyBpbiBkaWZmZXJlbnQgY2l0aWVzLCBub3Qgb25lIG9mIHRoZSBmaXZlIGJvcm91Z2hzLiBTaW5jZSBzaGUgaGFzIGRhdGEgaW4gdGhlc2UgemlwY29kZXMsIHdlIHdpbGwgYmUgYWRkaW5nIHRoZW0gYmFjayBpbnRvIHRoZSBjZW5zdXMgZGF0YWZyYW1lLiBUaGUgMTggemlwY29kZXMgd2hpY2ggd2UgaGFkIGRhdGEgb24gY29ycmVzcG9uZGVkIG1vc3RseSB0byBkYXRhIGluIFF1ZWVucyB3aGljaCB3ZXJlIG5vdCBsYWJlbGxlZCBhcyBRdWVlbnMuIFRoaXMgYWxzbyBleHBsYWlucyB0aGUgbGFjayBvZiBhbnkgaW5jb21lIGRhdGEgaW4gc29tZSBvZiB0aGUgbmVpZ2hib3Job29kcy4NCg0KV2UgYWxzbyBmb3VuZCB0aGF0IDYgemlwY29kZXMgYXJlIHN0aWxsIG1pc3NpbmcgZnJvbSBvdXIgaW5jb21lIGRhdGEuIFRoZXNlIGFyZSAxMDA2NSAoVXBwZXIgRWFzdCBTaWRlKSwgMTAwNzUoVXBwZXIgRWFzdCBTaWRlKSwgMTAxMDYobWlkdG93biksIDEwMjgxIChXb3JsZCBUcmFkZSksIDExMTA5IChMb25nIElzbGFuZCkgYW5kIDExMjQ5KFdpbGxpYW1zYnVyZykuDQoNCmBgYHtyfQ0Kc3VwZXJ6aXA0IDwtIHJiaW5kKHN1cGVyemlwMywgZmlsdGVyZWRfbWlzc2luZ196aXBzKQ0KbWFzdGVyX3ppcF9hZGRlZCA8LSBmdWxsX2pvaW4oc3VwZXJ6aXA0LCBuZWlnaGJvcmhvb2QyLCBieSA9ICd6aXBjb2RlJykNCmNvbG5hbWVzKG1hc3Rlcl96aXBfYWRkZWQpWzExXSA8LSAiYXJlYSINCkFWR19JTkNPTUVfTkVJR0hCT1JIT09EX0FEREVEIDwtIG1hc3Rlcl96aXBfYWRkZWQgJT4lIGdyb3VwX2J5KGFyZWEpICU+JSBzdW1tYXJpc2UoYXZnID0gbWVhbihpbmNvbWUuLCBuYS5ybT1UUlVFKSkNCm1hc3Rlcl96aXAyX2FkZGVkIDwtIGxlZnRfam9pbihtYXN0ZXJfemlwX2FkZGVkLCBBVkdfSU5DT01FX05FSUdIQk9SSE9PRF9BRERFRCwgYnkgPSAiYXJlYSIpDQpjZW5zdXNfYWRkZWQgPC0gbWVyZ2UobWFzdGVyX3ppcDJfYWRkZWQsIHppcGNvZGUsIGJ5LnggPSAnemlwY29kZScsIGJ5LnkgPSAnemlwJykNCmNlbnN1c19hZGRlZDIgPC0gY2Vuc3VzX2FkZGVkICU+JSBmaWx0ZXIoIWlzLm5hKGF2ZykpDQp3cml0ZS5jc3YoY2Vuc3VzX2FkZGVkMiwgZmlsZSA9ICJ6aXBfbWFzdGVyX25vX21pc3NpbmcuY3N2IikNCmBgYA0KDQpIZXJlIEkgd2VudCBiYWNrIGFuZCBhZGRlZCBpbiB0aGUgbWlzc2luZyB6aXAgY29kZXMuIEkgdGhlbiBoYWQgdG8gcmVjb21wdXRlIHRoZSBhdmVyYWdlIHN0YXRpc3RpYyBhY2NvdW50aW5nIGZvciB0aGUgbmV3IHppcGNvZGVzLiBJIHJlcGVhdGVkIHRoZSBzYW1lIHByb2Nlc3MgYXMgYmVmb3JlIG9uY2UgSSBhZGRlZCB0aGUgemlwY29kZXMgYmFjayBpbi4NCg0KTm93IHdlIGFyZSBsb2FkaW5nIGJhY2sgaW4gdGhlIGdvb2QgZGF0YSBhZnRlciB3cml0aW5nIHNvbWUgdG8gdGhlIGZvbGRlci4NCmBgYHtyfQ0KY2Vuc3VzIDwtIHJlYWQuY3N2KCJEYXRhL3ppcHNfbWFzdGVyX25vX21pc3NpbmdfbmJyaC5jc3YiKQ0KYGBgDQoNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmdncGxvdChjZW5zdXMsIGFlcyhyZW9yZGVyKGNpdHkueCwgLWF2ZywgRlVOID0gbWVkaWFuKSwgYXZnKSkgKyBnZW9tX2JveHBsb3QodmFyd2lkdGggPSBUUlVFKSArIGdndGl0bGUoIkJveHBsb3RzIG9mIEluY29tZSBieSBCb3JvdWdoIikgKyBsYWJzKHg9IkJvcm91Z2giLCB5PSJBdmVyYWdlIEluY29tZSBwZXIgbmVpZ2hib3Job29kIikgKyBjb29yZF9mbGlwKCkrdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkNCmBgYA0KDQpIZXJlIHdlIHNlZSB0aGUgZGlzdHJpYnV0aW9ucyBvZiB0aGUgYXZlcmFnZSBpbmNvbWUgcGVyIGFyZWEgaW4gZWFjaCBvZiB0aGUgNSBib3JvdWdocy4gVGhpcyBwbG90IGFwcGVhcnMgdG8gc2hvdyB0aGF0IFF1ZWVucyBWaWxsZ2UgZG9lcyBub3QgaGF2ZSB2ZXJ5IG11Y2ggZGF0YSBpbiBpdCBhcyB0aGlzIGlzIGEgdmFyaWFibGUgd2lkdGggYm94cGxvdC4gSG93ZXZlciwgaXQgdHVybnMgb3V0IGl0IGlzIGJlY2F1c2UgYWxsIG9mIHRoZSBvdGhlciBzaW5nbGUgcG9pbnQgbmVpZ2hib3Job29kcyBhcmUgYWN0dWFsbHkgYSBwYXJ0IG9mIFF1ZWVucy4gVGhpcyBpcyBhIHByb2JsZW0gd2UgaGFkIHRvIGZpeC4gVGhlc2UgYXJlIHRoZSAxOCB6aXBjb2RlcyB3ZSBhZGRlZCBiYWNrIGludG8gdGhlIGRhdGEuDQoNCmBgYHtyfQ0KY2Vuc3VzJGNpdHkueFshKGNlbnN1cyRjaXR5LnkgJWluJSBjKCJCcm9va2x5biIsICJOZXcgWW9yayIsICJCcm9ueCIsICJTdGF0ZW4gSXNsYW5kIikpXSA8LSAiUXVlZW5zIFZpbGxhZ2UiDQpjZW5zdXMkY2l0eS55WyEoY2Vuc3VzJGNpdHkueSAlaW4lIGMoIkJyb29rbHluIiwgIk5ldyBZb3JrIiwgIkJyb254IiwgIlN0YXRlbiBJc2xhbmQiKSldIDwtICJRdWVlbnMgVmlsbGFnZSINCmBgYA0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZ2dwbG90KGNlbnN1cywgYWVzKHJlb3JkZXIoY2l0eS54LCAtYXZnLCBGVU4gPSBtZWRpYW4pLCBhdmcpKSArIGdlb21fYm94cGxvdCh2YXJ3aWR0aCA9IFRSVUUpICsgZ2d0aXRsZSgiQm94cGxvdHMgb2YgSW5jb21lIGJ5IEJvcm91Z2giKSArIGxhYnMoeD0iQm9yb3VnaCIsIHk9IkF2ZXJhZ2UgSW5jb21lIHBlciBuZWlnaGJvcmhvb2QiKSt0aGVtZV9maXZldGhpcnR5ZWlnaHQoKQ0KYGBgDQoNCg0KQWxzbywgdGhlcmUgaXMgdmVyeSBsaXR0bGUgc3ByZWFkIGluIHRoZSBkaXN0cmlidXRpb24gZm9yIEJyb254IGFuZCBCcm9va2x5biBjb21wYXJlZCB0byBNYW5oYXR0YW4sIHdoaWNoIGhhcyBhIG1hc3NpdmUgc3ByZWFkLiBUaGUgbWlzc2luZyB2YWx1ZXMgb2NjdXIgZnJvbSB6aXBjb2RlcyB3aGljaCBvY2N1ciBpbiB0aGUgbmVpZ2hib3Job29kIGRhdGEgYW5kIG5vdCBpbiB0aGUgc3VwZXJ6aXAgZGF0YS4gV2UgY2FuIHNlZSB0aGF0IHRoZSBhdmVyYWdlIGluY29tZSBjYWxjdWxhdGVkIGZvciB0aGVzZSB6aXBjb2RlcyBpbiBnZW5lcmFsIGFyZSBoaWdoLiBXZSBjYW4gdGh1cyBpbmZlciB0aGV5IGFyZSBsaWtlbHkgZnJvbSBOZXcgWW9yayBhbmQgbG9va2luZyBiYWNrIGF0IHRoZSBjZW5zdXMgZGF0YSBhbmQgZmlsdGVyaW5nIGZvciB0aGUgTkFzLCB3aGljaCB3YXMgaW5kZWVkIHRoZSBjYXNlLg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZ2dwbG90KGNlbnN1cywgYWVzKHJlb3JkZXIoY2l0eS55LCAtYXZnLCBGVU4gPSBtZWRpYW4pLCBhdmcpKSArIGdlb21fYm94cGxvdCh2YXJ3aWR0aCA9IFRSVUUpICsgZ2d0aXRsZSgiQm94cGxvdHMgb2YgSW5jb21lIGJ5IEJvcm91Z2giKSArIGxhYnMoeD0iQm9yb3VnaCIsIHk9IkF2ZXJhZ2UgSW5jb21lIHBlciBuZWlnaGJvcmhvb2QiKSt0aGVtZV9maXZldGhpcnR5ZWlnaHQoKQ0KYGBgDQoNCkhlcmUgd2Ugc2VlIHdpdGggdGhlIHBsb3Qgd2l0aCB0aGUgbnVsbCB2YWx1ZXMgcmVtb3ZlZC4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmNlbnN1cyAlPiUgZmlsdGVyKCFpcy5uYShjZW5zdXMkY2l0eS54KSkgJT4lIGdncGxvdChhZXMoY2l0eS54LCBpbmNvbWUuLCBjb2xvcj1jaXR5LngsIGFscGhhKDAuMSkpKSArIGdlb21fcG9pbnQoc2l6ZSA9IDIsIHNoYXBlID0gMSkgKyBndWlkZXMoY29sb3I9RkFMU0UpICsgZ2d0aXRsZSgiU3RyaXAgUGxvdCBvZiBJbmNvbWVzIGJ5IEJvcm91Z2giKSArIGxhYnMoeD0iQm9yb3VnaCIsIHk9IkluY29tZSIpK3RoZW1lX2ZpdmV0aGlydHllaWdodCgpDQpgYGANCg0KU2ltaWxhcmx5IHRvIHRoZSBib3hwbG90LCB0aGlzIHNob3dzIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIGluY29tZSBmb3IgZWFjaCBvZiB0aGUgYm9yb3VnaHMuIFdlIGNhbiBzZWUgd2UgZG8gbm90IGhhdmUgdG9vIG11Y2ggZGF0YSBvbiBTdGF0ZW4gSXNsYW5kIGFnYWluLg0KDQpgYGB7cn0NCmdncGxvdChjZW5zdXMsIGFlcyh4PXJlb3JkZXIoY2l0eS54LCAtdGFibGUoY2l0eS54KVtjaXR5LnhdKSkpICsgZ2VvbV9iYXIoKSArIHhsYWIoIkNpdHkiKSArIHlsYWIoIk51bWJlciBvZiBaaXBjb2RlcyIpICsgZ2d0aXRsZSgiTnVtYmVyIG9mIHppcGNvZGVzIHdpdGggb2JzZXJ2YXRpb25zIHBlciBib3JvdWdoIikrdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkNCmBgYA0KDQpBZ2FpbiB0aGUgTkFzIGhlcmUgY29ycmVzcG9uZCB0byBub3QgaGF2aW5nIGFuIGFjdHVhbCBpbmNvbWUgdmFsdWUgZm9yIHNhaWQgemlwY29kZS4gSXQgd2FzIGRlcml2ZWQgZnJvbSB0aGUgYXZlcmFnZSBmb3IgdGhhdCBuZWlnaGJvcmhvb2QuIFRoaXMgc2hvd3MgdGhhdCBhcHByb3hpbWF0ZWx5IDI1IG9mIHRoZSB6aXBjb2RlcyBoYWQgbm8gaW5jb21lIGRhdGEgYW5kIHdlIHdlcmUgZm9yY2VkIHRvIGltcHV0ZSBpdCB3aXRoIHRoZSBhdmVyYWdlIGZvciB0aGF0IG5laWdoYm9yaG9vZC4gQWxzbywgdGhpcyBkYXRhIGlzIGNlcnRhaW5seSBiZXR0ZXIgdGhhbiB0aGUgbGFzdCBzZXQsIGFsdGhvdWdoIGlkZWFsbHkgd2Ugd291bGQgc3RpbGwgaGF2ZSBtb3JlIGRhdGEgb24gUXVlZW5zIGFuZCBTdGF0ZW4gSXNsYW5kIGVzcGVjaWFsbHkgaWYgdGhlcmUgYXJlIG1hbnkgbGlxdW9yIGxpY2Vuc2VzIGFuZCBzaWRld2FsayBjYWZlcyBwb3BwaW5nIHVwIGluIHppcGNvZGVzIHdoaWNoIHdlIGFyZSBtaXNzaW5nLg0KDQojIyMjIFNpZGV3YWxrIENhZmUgTGljZW5zZSBEYXRhDQoNCmBgYHtyfQ0KcXVlZW5zX25hbWVzPC1yZWFkLmNzdigiRGF0YS9RVV9Mb2NhdGlvbnMuY3N2IixzdHJpcC53aGl0ZT1UUlVFKQ0KYnJvb2tseW5fbmFtZXM8LXJlYWQuY3N2KCJEYXRhL0JLX0xvY2F0aW9ucy5jc3YiLHN0cmlwLndoaXRlPVRSVUUpDQptYW5oYXR0YW5fbmFtZXM8LXJlYWQuY3N2KCJEYXRhL01BX0xvY2F0aW9ucy5jc3YiLHN0cmlwLndoaXRlPVRSVUUpDQpicm9ueF9uYW1lczwtcmVhZC5jc3YoIkRhdGEvQlhfTG9jYXRpb25zLmNzdiIsc3RyaXAud2hpdGU9VFJVRSkNCg0Kc2lkZXdhbGtzPC1yZWFkLmNzdigiRGF0YS9TaWRld2FsayBDYWZlcy9TaWRld2Fsa19MaWNlbnNlcy5jc3YiLHN0cmlwLndoaXRlPVRSVUUsIG5hLnN0cmluZ3M9YygiIiwiTkEiKSkNCmBgYA0KIyMjIyNEYXRhIFF1YWxpdHkNCk9uZSBvZiB0aGUgbW9zdCBnbGFyaW5nIGlzc3VlcyBpbiBkYXRhIHF1YWxpdHkgaXMgdGhlIG5hbWluZyBvZiB0aGUgY2l0aWVzIGluIE1hbmhhdHRhbiBhbmQgUXVlZW5zLiBDYXBpdGFsaXphdGlvbiBkaWZmZXJlbmNlcyBsaWtlIGJldHdlZW4gIk5FVyBZT1JLIiBhbmQgIk5ldyBZb3JrIiBncm91cGVkIGNhZmVzIGluIHRoZSBzYW1lIGNpdHkgdG8gYmUgY2F0ZWdvcml6ZWQgZGlmZmVyZW50bHkuIFNpbWlsYXJseSwgY2l0aWVzIGluIFF1ZWVucyBoYWQgc29tZSBhYmJyZXZpYXRpb24gZGlmZmVyZW5jZXMgbGlrZSBiZXR3ZWVuICJMT05HIElTTEFORCBDSVRZIiBhbmQgIkxPTkcgSVMgQ0lUWSIgb3IgIkpBQ0tTT04gSEVJR0hUUyIgYW5kICJKQUNLU09OIEhUUyIuIEkgbWFudWFsbHkgcmVwbGFjZWQgYWxsIGFiYnJldmlhdGVkIG9yIG5vbi1jYXBpdGFsaXplZCBjaXRpZXMgd2l0aCB0aGVpciBsb25nZXIsIGNhcGl0YWxpemVkIGZvcm1zLg0KYGBge3J9DQpzaWRld2Fsa3MkQ0lUWVtzaWRld2Fsa3MkQ0lUWT09IkxPTkcgSVMgQ0lUWSJdPC0iTE9ORyBJU0xBTkQgQ0lUWSINCnNpZGV3YWxrcyRDSVRZW3NpZGV3YWxrcyRDSVRZPT0iTUlERExFIFZMRyJdPC0iTUlERExFIFZJTExBR0UiDQpzaWRld2Fsa3MkQ0lUWVtzaWRld2Fsa3MkQ0lUWT09IkpBQ0tTT04gSFRTIl08LSJKQUNLU09OIEhFSUdIVFMiDQpzaWRld2Fsa3MkQ0lUWVtzaWRld2Fsa3MkQ0lUWT09Ik5ldyBZb3JrIl08LSJORVcgWU9SSyINCmBgYA0KDQpUaGUgc2Vjb25kIGRhdGEgcXVhbGl0eSBpc3N1ZSBJIGVuY291bnRlcmVkIHdhcyB0aGUgbG9uZ2l0dWRlIGFuZCBsYXRpdHVkZSBvZiB0d28gcmVzdGF1cmFudHMuIFBsb3R0aW5nIGFsbCByZXN0YXVyYW50cyB1c2luZyBxbXBsb3QsIEkgZ290IHRoZSBmb2xsb3dpbmcgcmVzdWx0Og0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KcW1wbG90KExPTkdJVFVERSxMQVRJVFVERSxkYXRhPXNpZGV3YWxrcyxtYXB0eXBlPSJ0b25lci1saXRlIixjb2xvcj1JKCJwdXJwbGUiKSkNCmBgYA0KTm90ZSB0aGF0IHRoZXJlIGlzIGEgbXlzdGVyaW91cyBkb3QgYWxsIHRoZSB3YXkgd2VzdCBvZiBIYXJyaXNidXJnLCBQQS4gVGhpcyBzaG91bGQgbm90IGJlIHRoZSBjYXNlIHNpbmNlIHRoZSBkYXRhIHNob3VsZCBvbmx5IGFwcGx5IHRvIHRoZSBDaXR5IGFuZCBib3JvdWdocyBvZiBOZXcgWW9yaywgc28gSSBwbG90dGVkIHRoZSBsb25naXR1ZGUgYW5kIGxhdGl0dWRlIHRvIHNlZSBhbnkgb3V0bGllcnMuDQpgYGB7cn0NCmxhdDwtZ2dwbG90KHNpZGV3YWxrcywgYWVzKHg9TEFUSVRVREUpKStnZW9tX2hpc3RvZ3JhbShiaW53aWR0aD0uMDEpK2dndGl0bGUoIkxhdGl0dWRlIEhpc3RvZ3JhbSIpK3RoZW1lX2ZpdmV0aGlydHllaWdodCgpDQpsb25nPC1nZ3Bsb3Qoc2lkZXdhbGtzLGFlcyh4PUxPTkdJVFVERSkpK2dlb21faGlzdG9ncmFtKGJpbndpZHRoPS4wMSkrZ2d0aXRsZSgiTG9uZ2l0dWRlIEhpc3RvZ3JhbSIpK3RoZW1lX2ZpdmV0aGlydHllaWdodCgpDQpncmlkLmFycmFuZ2UobGF0LGxvbmcsbnJvdz0yKQ0KYGBgDQpZb3UgY2FuIGJhcmVseSBzZWUgc29tZSBwb2ludHMgYmVvdyB0aGUgNDAuMjUgbGF0aXR1ZGUgYW5kIC03NyBsb25naXR1ZGUsIHdoaWNoIHNlZW0gdG8gYmUgcXVpdGUgb2ZmIGZyb20gdGhlIHJlc3QuIE5leHQsIEkgem9vbWVkIGluIG9uIHRob3NlIHZhbHVlczoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsb25nPC1nZ3Bsb3Qoc2lkZXdhbGtzLGFlcyh4PUxPTkdJVFVERSkpK2dlb21faGlzdG9ncmFtKGJpbndpZHRoPS4yNSkrZ2d0aXRsZSgiWm9vbWVkIExvbmdpdHVkZSBIaXN0b2dyYW0iKSt4bGltKC04MCwtNzcpDQpsb25nDQpgYGANClRoZXJlIGFyZSB0d28gZGF0YXBvaW50cyB0aGF0IGhhdmUgYW4gYWJub3JtYWxseSBsb3cgTGF0aXR1ZGUgYW5kIExvbmdpdHVkZSBhcyBjb21wYXJlZCB0byB0aGUgcmVzdCBvZiB0aGUgZGF0YS4gVGFraW5nIGEgbG9vayBhdCB0aGVzZSBkYXRhIHBvaW50cywgSSBpZGVudGlmaWVkIHRoZW0gYXMgdGhlIGZvbGxvd2luZyBidXNpbmVzc2VzOg0KYGBge3J9DQpzaWRld2Fsa3MgJT4lIGZpbHRlcihMT05HSVRVREUgPCAtNzcpICU+JSBzZWxlY3QoQlVTSU5FU1NfTkFNRTIsTE9OR0lUVURFLExBVElUVURFKQ0KYGBgDQpMb29raW5nIHRoZXNlIGxvY2F0aW9ucyB1cCBvbiBHb29nbGUsIGl0IGxvb2tzIGxpa2UgdGhlcmUgd2FzIGFuIGVycm9yIGluIGVudGVyaW5nIHRoZWlyIGxvbmdpdHVkZSBhbmQgbGF0aXR1ZGUuIFRoZSBjb3JyZWN0IHZhbHVlcyBhcmUgKDQwLjcyMDE3LC03My45OTY4KSBmb3IgTXVsYmVycnkgU3RyZWV0IEJhciBhbmQgKDQwLjc5NTkzLC03My45MzU3KSBmb3IgUHJpbWUgT25lIDE2LiBJIGhhdmUgY29ycmVjdGVkIHRoZXNlIHZhbHVlcyBpbiBhbm90aGVyIENTViwgd2hpY2ggSSB3aWxsIHVzZSBmcm9tIGhlcmUgb24uDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpzaWRld2Fsa3M8LXJlYWQuY3N2KCJEYXRhL1NpZGV3YWxrIENhZmVzL1NpZGV3YWxrX0xpY2Vuc2VzMi5jc3YiLHN0cmlwLndoaXRlPVRSVUUsIG5hLnN0cmluZ3M9YygiIiwiTkEiKSkNCnNpZGV3YWxrcyRDSVRZW3NpZGV3YWxrcyRDSVRZPT0iTE9ORyBJUyBDSVRZIl08LSJMT05HIElTTEFORCBDSVRZIg0Kc2lkZXdhbGtzJENJVFlbc2lkZXdhbGtzJENJVFk9PSJNSURETEUgVkxHIl08LSJNSURETEUgVklMTEFHRSINCnNpZGV3YWxrcyRDSVRZW3NpZGV3YWxrcyRDSVRZPT0iSkFDS1NPTiBIVFMiXTwtIkpBQ0tTT04gSEVJR0hUUyINCnNpZGV3YWxrcyRDSVRZW3NpZGV3YWxrcyRDSVRZPT0iTmV3IFlvcmsiXTwtIk5FVyBZT1JLIg0KDQpxbXBsb3QoTE9OR0lUVURFLExBVElUVURFLGRhdGE9c2lkZXdhbGtzLG1hcHR5cGU9InRvbmVyLWxpdGUiLGNvbG9yPUkoInB1cnBsZSIpKQ0KDQpgYGANCk5vdyB0aGF0IHRoZSBvZmZlbmRpbmcgZGF0YXBvaW50cyBoYXZlIGJlZW4gZml4ZWQsIHRoZSBxbXBsb3QgYWNjdXJhdGVseSB6b29tcyBpbiBvbiB0aGUgTmV3IFlvcmsgYXJlYS4gSG93ZXZlciwgc2luY2UgdGhvc2UgdHdvIGRhdGEgcG9pbnRzIGV4aXN0LCBpdCBpcyBlbnRpcmVseSBwb3NzaWJsZSB0aGF0IHRoZXJlIGFyZSBvdGhlciBtaXMtbWFwcGVkIGxhbmdpdHVkZSBhbmQgbG9uZ2l0dWRlIHBvaW50cy4gSG93ZXZlciwgd2l0aG91dCBtYW51YWxseSBnb2luZyB0aHJvdWdoIGVhY2ggZGF0YSBwb2ludCwgaXQgaXMgbm90IGVhc3kgdG8gZGVkdWNlIHdoZXJlLg0KDQojIyMjIyBNaXNzaW5nIERhdGENClRvIGZ1cnRoZXIgaW52ZXN0aWdhdGUgZGF0YSBxdWFsaXR5LCBJIHRvb2sgYSBsb29rIGF0IHRoZSBtYWluIGNvbHVtbnMgdGhhdCBpZGVudGlmaWVkIGEgYnVzaW5lc3MgLSBpdHMgbGljZW5zZSBudW1iZXIsIGxpY2Vuc2Ugc3RhdHVzLCBidXNpbmVzcyBuYW1lIChicm9rZW4gaW50byB0d28pLCBidWlsZGluZyBudW1iZXIsIHN0cmVldCwgY2l0eSwgc3RhdGUgYW5kIHppcC4gVG8gZGlzcGxheSB0aGlzIGRhdGEsIEkgdXNlZCBhIHZpc25hIHBsb3Qgd2hpY2ggaGlnaGxpZ2h0cyBhbnkgY29sdW1ucyB3aXRoIG1pc3NpbmcgdmFsdWVzIGFuZCBzaG93cyB0aGUgZnJlcXVlbmN5IG9mIHRoZSBjb21iaW5hdGlvbnMgb2YgdGhvc2UgY29sdW1ucy4NCmBgYHtyIGZpZy5oZWlnaHQ9Nn0NCnZpc25hKHNpZGV3YWxrc1ssMTo5XSkNCmBgYA0KVGhlIHZpc25hIHNob3dzIHRoYXQgb25seSB0d28gdmFyaWFibGVzIGhhdmUgbWlzc2luZyB2YWx1ZXMgLSBsaWNlbnNlIG51bWJlciBhbmQgYnVzaW5lc3MgbmFtZSBudW1iZXIgMi4gVGhlc2UgYXJlIGV4cGVjdGVkIC0gb25seSBsaWNlbnNlcyB0aGF0IGhhdmUgYmVlbiBhcHByb3ZlZCB3b3VsZCBoYXZlIGEgbGljZW5zZSBudW1iZXIsIGFuZCBzb21lIGJ1c2luZXNzZXMgZG8gbm90IG5lZWQgdG8gdXNlIGEgc2Vjb25kIGxpbmUgZm9yIHRoZWlyIGJ1c2luZXNzIG5hbWUuICAgDQoNCk5leHQsIEkgbG9va2VkIGF0IGFkZGl0aW9uYWwgYXBwbGljYXRpb24gaW5mb3JtYXRpb24gd2hpY2ggaW5jbHVkZXMgdGhlIHNpZGV3YWxrIGNhZmUgdHlwZSwgdGhlIHNxdWFyZSBmb290YWdlIHJlcXVlc3RlZCwgbnVtYmVyIG9mIHRhYmxlcywgbnVtYmVyIG9mIGNoYWlycywgRGVwYXJ0bWVudCBvZiBIZWFsdGggYW5kIE1lbnRhbCBIeWdpZW5lIChET0hNSCkgaWRlbnRpZmljYXRpb24gbnVtYmVyLCB0aGUgcmVzdGF1cmFudCdzIGxvbmdpdHVkZSBhbmQgbGF0aXR1ZGUsIHRoZSBjb21tdW5pdHkgZGlzdHJpY3QgYW5kIGNpdHkgY291bmNpbCBkaXN0cmljdCBpdCBiZWxvbmdzIHRvLCBhbmQgdGhlIFVSTCBmb3IgdGhlIHdlYnNpdGUgb2YgdGhlIE5ZQyBDb21tdW5pdHkgRGlzdHJpY3QuIA0KDQpgYGB7ciBmaWcuaGVpZ2h0PTZ9DQp2aXNuYShzaWRld2Fsa3NbLDEwOjE5XSkNCmBgYA0KVHdvIHZhcmlhYmxlcyBoYXZlIG1pc3NpbmcgdmFsdWVzIC0gdGhlIHNxdWFyZSBmb290YWdlIG9mIHRoZSBzaWRld2FsayBjYWZlIGFuZCB0aGUgRGVwYXJ0bWVudCBvZiBIZWFsdGggYW5kIE1lbnRhbCBIeWdpZW5lIGlkZW50aWZpY2F0aW9uIG51bWJlci4gVGhlc2UgYXJlIGl0ZW1zIHRoYXQgdGhlIHJlc3RhdXJhbnQgd291bGQgaGF2ZSB0byBwcm92aWRlIGR1cmluZyB0aGVpciBhcHBsaWNhdGlvbiBwcm9jZXNzLCBhbmQgbWF5IG5vdCBoYXZlIGJlZW4gYXZhaWxhYmxlIGF0IHN1Ym1pc3Npb24uIFRoZXNlIG1pc3NpbmcgdmFsdWVzIHNob3VsZCBub3QgaW1wZWRlIHdpdGggb3VyIGFuYWx5c2lzLiBIb3dldmVyLCBpZiB3ZSB3YW50ZWQgdG8gY3Jvc3MtcmVmZXJlbmNlIHRoZSBoZWFsdGggZ3JhZGVzIGZyb20gdGhlIERPSE1IIGRhdGFzZXRzLCB3ZSB3b3VsZCBoYXZlIGlzc3VlcyB3aXRoIHRoZSBtaXNzaW5nIElEIG51bWJlcnMuICANCg0KVGhlIG5leHQgc2V0IG9mIGNvbHVtbnMgdG8gYW5hbHl6ZSBpcyB0aGUgc2V0IG9mIGFwcGxpY2F0aW9uLXNwZWNpZmljIGZpZWxkcy4gVGhlc2UgaW5jbHVkZSB0aGUgYXBwbGljYXRpb24gSUQsIGFuZCB0aGUgc2lkZXdhbGsgY2FmZSB0eXBlLCBzcXVhcmUgZm9vdGFnZSwgbnVtYmVyIG9mIHRhYmxlcyBhbmQgY2hhaXJzIHByb3ZpZGVkIGJ5IHRoZSBhcHBsaWNhbnQuIEl0IGhhcyB0aGUgYXBwIHN0YXR1cywgdGhlIGRhdGUgYXQgd2hpY2ggdGhlIGFwcCBzdGF0dXMgd2FzIHJlYWNoZWQsIGV4cGlyYXRpb24gZGF0ZSwgdGhlIGV4cGlyYXRpb24gZGF0ZSBvZiB0aGUgdGVtcG9yYXJ5IG9wZXJhdGluZyBvcmRlcigiVE9PIiAtIGlmIGF2YWlsYWJsZSksIHRoZSBhcHBsaWNhdGlvbiBzdWJtaXQgZGF0ZSwgYW5kIHdoZXRoZXIgdGhlIGFwcGxpY2F0aW9uIGhhcyBiZWVuIHJlY2VpdmVkLg0KDQpgYGB7ciBmaWcuaGVpZ2h0PTd9DQp2aXNuYShzaWRld2Fsa3NbLDIwOjI5XSkNCmBgYA0KVGhlcmUgc2VlbSB0byBiZSBhIGZldyBtaXNzaW5nIGZpZWxkcyBpbiB0aGUgcHJvdmlkZWQgc2lkZXdhbGsgY2FmZSBzcXVhcmVmb290YWdlIGRhdGEuIEhvd2V2ZXIsIHdlIHdpbGwgbm90IGJlIGZvY3VzaW5nIG9uIHRoaXMgZGF0YSBwb2ludCBpbiBvdXIgYW5hbHlzaXMuIEV4cGlyYXRpb24gZGF0ZXMgYXJlIGFsc28gbWlzc2luZyBmcm9tIGNlcnRhaW4gbGljZW5zZXMuIEZpbHRlcmluZyBvdXQgdGhvc2UgbGljZW5zZXMgYW5kIGxvb2tpbmcgYXQgdGhlaXIgQVBQX1NUQVRVUywgd2UgY2FuIHNlZSB0aGF0IHRob3NlIGxpY2Vuc2VzIHRoYXQgYXJlIGluIHJldmlldyBtYXkgbm90IGhhdmUgYW4gYXNzaWduZWQgZXhwaXJhdGlvbiBzdGF0dXMuIEluZGVlZCwgdGhpcyBtYWtlcyBzZW5zZSB3aXRoIG5ldyBhcHBsaWNhdGlvbnMgdGhhdCBoYXZlIG5vdCBiZWVuIGdpdmVuIGFuIGV4cGlyYXRpb24gZGF0ZS4NCg0KYGBge3J9DQptaXNzaW5nX2V4cGlyYXRpb248LXNpZGV3YWxrcyAlPiUgZmlsdGVyKGlzLm5hKEVYUElSQVRJT05fREFURSkpICU+JSBzZWxlY3QoQVBQX1NUQVRVUykNCm1pc3NpbmdfZXhwaXJhdGlvbiAlPiUgdW5pcXVlKCkNCmBgYA0KQSBzaW1pbGFyIGV4cGxhbmF0aW9uIGFyaXNlcyBhYm91dCB0aGUgYXBwbGljYXRpb25zIHdpdGggYSBtaXNzaW5nIFRlbXBvcmFyeSBPcGVyYXRpbmcgT3JkZXIgKCJUT08iKSBkYXRlIC0gb25seSB0aG9zZSBhcHBsaWNhdGlvbnMgdGhhdCBoYXZlIGJlZW4gZ3JhbnRlZCBhIFRPTyBpbiBjYXNlcyB3aGVyZSB0aGUgb2xkIGxpY2Vuc2UgaGFzIGV4cGlyZWQgYnV0IHRoZSByZW5ld2FsIGlzIGluIHRoZSBwcm9jZXNzIG9mIGJlaW5nIHJldmlld2VkLiAgDQoNClRoZSByZW1haW5pbmcgY29sdW1zIHRyYWNrIHRoZSBzdGF0dXMgb2YgdGhlIGxpY2Vuc2UgYXBwcm92YWwgcHJvY2VzcyB0aHJvdWdoIHZhcmlvdXMgc3RhZ2VzIGFuZCB3aWxsIG5vdCBiZSB1c2VkIGluIHRoaXMgYW5hbHlzaXMuIFRoZXJlZm9yZSwgSSB3aWxsIG5vdCBzcGVuZCB0aW1lIG9uIGludmVzdGlnYXRpbmcgYW55IG1pc3NpbmcgZGF0YS4gDQoNCiMjIyMjIENvbWJpbmluZyB3aXRoIE5laWdoYm9yaG9vZCBkYXRhDQpGcm9tIEFkYW0ncyBhbmFseXNpcywgd2UgaGF2ZSBhIG1hc3Rlcl96aXAgbGlzdCBvZiBhbGwgb2YgdGhlIHppcCBjb2RlcyBtYXBwZWQgdG8gdGhlIG5laWdoYm9yaG9vZHMgaW4gZWFjaCBib3JvdWdoLiBJIHdpbGwgY3JlYXRlIGEgc2VwYXJhdGUgZGF0YWZyYW1lIGNhbGxlZCBzaWRld2Fsa3NfbmJoIHdoaWNoIGlzIGpvaW5lZCB3aXRoIHRoZSBtYXN0ZXJfemlwIG9uIFppcCBDb2RlLiBJIHdpbGwgdXNlIHRoZSBuZWlnaGJvcmhvb2QgY29sdW1uLCAnbmJoJywgdG8gZ3JvdXAgdGhlIHNpZGV3YWxrIGNhZmVzIGJ5IGJvcm91Z2ggYW5kIG5laWdoYm9yaG9vZC4gSSB0aGVuIGZpbHRlcmVkIHRoZSByZXN1bHRpbmcgZGF0YWZyYW1lIHRvIGZpbmQgYW55IG1pc3NpbmcgdmFsdWVzIGZyb20gdGhlIGpvaW4uDQoNCmBgYHtyfQ0KDQptYXN0ZXJfemlwPC1yZWFkLmNzdigiRGF0YS96aXBzX21hc3Rlcl9ub19taXNzaW5nX25icmguY3N2Iiwgc3RyaXAud2hpdGU9VFJVRSkNCg0Kc2lkZXdhbGtzX25iaCA8LSBtZXJnZShzaWRld2Fsa3MsIG1hc3Rlcl96aXAsIGJ5PSJaSVAiLCBhbGwueD1UUlVFKQ0KDQpzaWRld2Fsa3NfbmJoICU+JSBmaWx0ZXIoaXMubmEobmJoKSkgJT4lIHNlbGVjdChaSVApDQpgYGANClRoZXJlIHdlcmUgYSBsb3Qgb2YgbWlzc2luZyB2YWx1ZXMgb3JpZ2luYWxseSBmcm9tIHRoZSBtYXN0ZXJfemlwIGFuYWx5c2lzLCB3aGljaCBJIGhhdmUgbWFudWFsbHkgcmVzZWFyY2hlZCBhbmQgZmlsbGVkIGluIGluIHRoZSBfbmJyaC5jc3YuIE5vdyB0aGVyZSBhcmUgbm8gQ2FmZXMgd2l0aG91dCBhIG5laWdoYm9yaG9vZCBkZXNpZ25hdGlvbi4gDQoNCg0KDQojIyMjIExpcXVvciBMaWNlbnNlIERhdGENCg0KYGBge3J9DQpueV9saXF1b3JfbGljZW5zZXMgPC0gcmVhZC5jc3YoJ0RhdGEvbGlxdW9yX2xpY2Vuc2VzL2FjdGl2ZV9saXF1b3IuY3N2JykNCmBgYA0KVGhpcyBpcyBhIGRhdGFmcmFtZSB3aGVyZSBlYWNoIHJvdyBjb3JyZXNwb25kcyB0byBhbiBhY3RpdmUgbGlxdW9yIGxpY2Vuc2UuIFRoZXJlIGFyZSBzZXZlcmFsIGNvbHVtbnMgaWRlbnRpZnlpbmcgdGhlIHR5cGUgb2YgbGlxdW9yIGxpY2Vuc2UsIHRoZSBkYXRlIHRoZSBsaWNlbnNlIGlzIGVmZmVjdGl2ZSwgYXMgd2VsbCBhcyBzb21lIGdlb2dyYXBoaWMgaW5mb3JtYXRpb24uDQoNCkFzIGEgc2FuaXR5IGNoZWNrLCBJIHdhbnRlZCB0byBtYWtlIHN1cmUgdGhhdCB0aGlzIGRhdGFzZXQgd2FzIG9ubHkgZm9yIE5ZIHN0YXRlIGRhdGEuDQpgYGB7cn0NCmxldmVscyhueV9saXF1b3JfbGljZW5zZXMkU3RhdGUpDQpgYGANCk11Y2ggdG8gbXkgc3VycHJpc2UsIGl0IHNlZW1zIHRoYXQgYWxsIHN0YXRlcyBhcmUgaW5jbHVkZWQhDQoNCmBgYHtyfQ0KbnlfbGlxdW9yX2xpY2Vuc2VzICU+JSBmaWx0ZXIoU3RhdGUgIT0gIk5ZIikgJT4lIGdyb3VwX2J5KExpY2Vuc2UuVHlwZS5OYW1lKSAlPiUgc3VtbWFyaXNlKGNvdW50ID0gbigpKQ0KYGBgDQpPdXQgb2YgdGhlIDEyODAgcmVjb3JkcyBvdXRzaWRlIG9mIE5ldyBZb3JrIDEyNTAgb2YgdGhlbSBjb3JyZXNwb25kIHRvIGRpcmVjdCB3aW5lIHNoaXBtZW50cyB3aGljaCBtYWtlcyBzZW5zZS4gDQoNCmBgYHtyfQ0KbnlfbGlxdW9yX2xpY2Vuc2VzICU+JSBmaWx0ZXIoTGljZW5zZS5UeXBlLk5hbWUgPT0gJ01BU1RFUiBGT0xERVIgU1RBVFVTIFJFQ09SRCcpICU+JSBoZWFkICU+JSANCiAgc2VsZWN0KFByZW1pc2VzLk5hbWUpDQpgYGANClRocm91Z2ggaW5zcGVjdGlvbiBvZiB0aGUgZGF0YWZyYW1lLCBJIG5vdGljZWQgYSBmZXcgdGhpbmdzLiBGaXJzdCwgYSBsb3Qgb2YgdGhlc2UgTUFTVEVSIEZPTERFUiBTVEFUVVMgUkVDT1JEIGxpY2Vuc2VzIGNvcnJlc3BvbmQgdG8gc3VwZXJtYXJrZXRzIGFuZCBiaWcgY2hhaW5zLiBCdXQgd2hhdCBhcHBlYXJzIG1vcmUgdHJvdWJsaW5nIGlzIHRoYXQgdGhlcmUgbWlnaHQgYmUgcmVjb3JkcyBvdXRzaWRlIG9mIE5ldyBZb3JrIENpdHkuIA0KDQpgYGB7ciwgZmlnLmhlaWdodD0xMH0NCm55X2xpcXVvcl9saWNlbnNlcyAlPiUgZ3JvdXBfYnkoQ2l0eSkgJT4lIA0KICBzdW1tYXJpc2UobnVtID0gbigpKSAlPiUgZmlsdGVyKG51bSA+IDEwMCkgJT4lIA0KICBnZ3Bsb3QoLiwgYWVzKHg9ZmN0X2luZnJlcShDaXR5KSwgeT1udW0pKSArIGdlb21fYmFyKHN0YXQgPSAnaWRlbnRpdHknKSArIGNvb3JkX2ZsaXAoKSt0aGVtZV9maXZldGhpcnR5ZWlnaHQoKQ0KYGBgDQpJbmRlZWQsIHRoaXMgZGF0YXNldCBpbmNsdWRlcyBsaXF1b3IgbGljZW5zZXMgYWNyb3NzIHRoZSBlbnRpcmUgc3RhdGUuIEZ1cnRoZXJtb3JlLCB0aGVzZSBkb24ndCBjb3JyZXNwb25kIHRvIHNoaXBtZW50cyBpbnRvIE5ldyBZb3JrIENpdHkgZWl0aGVyLg0KDQpgYGB7cn0NCm55X2xpcXVvcl9saWNlbnNlcyAlPiUgZmlsdGVyKENpdHkgPT0gJ0FMQkFOWScpICU+JSBmaWx0ZXIoTGljZW5zZS5UeXBlLk5hbWUgPT0gJ0dST0NFUlkgU1RPUkUgQkVFUicpICU+JSBoZWFkICU+JSANCiAgc2VsZWN0KFByZW1pc2VzLk5hbWUsIExpY2Vuc2UuU2VyaWFsLk51bWJlcikNCmBgYA0KSW4gb3JkZXIgdG8gbWFrZSB0aGlzIGFuYWx5c2lzIGNvbnNpc3RlbnQgd2l0aCB0aGUgb3RoZXIgZGF0YXNldHMsIEkgd2lsbCB1c2UgdGhlIHppcGNvZGUgY29sdW1uLiBFdmVuIGluIHRoZSBwbG90IGFib3ZlLCB3ZSBjYW4gc2VlIGNpdHkgbmFtZXMgbGlrZSBKYWNrc29uIEhlaWdodHMsIGFuZCBBc3RvcmlhLCB3aGljaCBhcmUgdHJhZGl0aW9uYWxseSB0aG91Z2h0IG9mIGFzIHBhcnQgb2YgTmV3IFlvcmsuIFRoZXJlZm9yZSwgZmlsdGVyaW5nIGJ5IGNpdHkgbmFtZSB3aWxsIGJlIGRpZmZpY3VsdC4gTHVja2lseSwgTWFyaWthIGFuZCBBZGFtIHdlcmUgYWJsZSB0byBnZW5lcmF0ZSBhIG1hc3RlciB6aXAgZmlsZSBmb3IgYWxsIG5laWdoYm9yaG9vZHMgaW4gTmV3IFlvcmsuIFRoZXJlZm9yZSwgSSB3aWxsIG1lcmdlIHRoaXMgZGF0YXNldCB3aXRoIHRoZSBtYXN0ZXIgZGF0YXNldC4gRmlyc3QsIGFzIGEgc2FuaXR5IGNoZWNrLCBhbGwgemlwIGNvZGVzIHNob3VsZCBoYXZlIDUgb3IgOSBjaGFyYWN0ZXJzOg0KDQpgYGB7cn0NCnN1bShpcy5uYShueV9saXF1b3JfbGljZW5zZXMkWmlwKSkNCmBgYA0KDQoNCmBgYHtyfQ0KbnlfbGlxdW9yX2xpY2Vuc2VzICU+JSBmaWx0ZXIobmNoYXIoYXMuY2hhcmFjdGVyKFppcCkpICE9IDUpICU+JSBmaWx0ZXIobmNoYXIoYXMuY2hhcmFjdGVyKFppcCkpICE9IDkpICU+JSANCiAgc2VsZWN0KFppcCwgQWN0dWFsLkFkZHJlc3Mub2YuUHJlbWlzZXMuLkFkZHJlc3MxLiwgQ2l0eSkNCmBgYA0KDQpUaGVyZSBzZWVtIHRvIGJlIHRocmVlIHppcCBjb2RlcyB3aXRoIHR5cG9zLCB3aGljaCBJIG1hbnVhbGx5IGdvb2dsZWQgYW5kIGNvbmZpcm1lZCB0aGF0IHRoZSB6aXAgY29kZXMgd2VyZSBvbmUgY2hhcmFjdGVyIG9mZi4gSGVuY2UsIEkgd2lsbCByZXBsYWNlIHRoZXNlIHppcCBjb2RlcyBieSB0aGUgcHJvcGVyIG9uZXMuIFRoZW4gSSB3aWxsIHRha2UgdGhlIGZpcnN0IGZpdmUgY2hhcmFjdGVycyBvZiBldmVyeSB6aXAgY29kZSB0byBrZWVwIGV2ZXJ5dGhpbmcgc3RhbmRhcmQuIFRoZW4sIEkgd2lsbCBiZSBhYmxlIHRvIG1lcmdlIHdpdGggdGhlIHppcCBjb2RlcyB0aGF0IE1hcmlrYSBhbmQgQWRhbSBjb21waWxlZC4NCg0KYGBge3J9DQp6aXBzIDwtIGxldmVscyhueV9saXF1b3JfbGljZW5zZXMkWmlwKQ0Kemlwc1t6aXBzID09ICIxMTIyMDkiXSA8LSAiMTEyMDkiDQp6aXBzW3ppcHMgPT0gIjEyMzgiXSA8LSAiMTEyMzgiDQp6aXBzW3ppcHMgPT0gIjEzNjkiXSA8LSAiMTEzNjkiDQpsZXZlbHMobnlfbGlxdW9yX2xpY2Vuc2VzJFppcCkgPC0gemlwcw0KbnlfbGlxdW9yX2xpY2Vuc2VzJFppcCA8LSBhcy5jaGFyYWN0ZXIobnlfbGlxdW9yX2xpY2Vuc2VzJFppcCkNCm55X2xpcXVvcl9saWNlbnNlcyA8LSBueV9saXF1b3JfbGljZW5zZXMgJT4lIG11dGF0ZShaaXAsIG1vZF96aXAgPSBzdWJzdHIoWmlwLCAxLCA1KSkNCm55X2xpcXVvcl9saWNlbnNlcyRtb2RfemlwIDwtIGFzLm51bWVyaWMobnlfbGlxdW9yX2xpY2Vuc2VzJG1vZF96aXApDQoNCnppcGNvZGVzIDwtIHJlYWQuY3N2KCdEYXRhL3ppcHNfbWFzdGVyX25vX21pc3NpbmdfbmJyaC5jc3YnKQ0KbnljX2xpcXVvcl9saWNlbnNlcyA8LSBtZXJnZShueV9saXF1b3JfbGljZW5zZXMsIHppcGNvZGVzLCBieS54ID0gIm1vZF96aXAiLCBieS55ID0gIlpJUCIsIGFsbC55ID0gVFJVRSkNCmBgYA0KDQpJIGluc3BlY3RlZCB3aGljaCB6aXAgY29kZXMgZGlkbid0IG1lcmdlIGNsZWFubHkNCmBgYHtyfQ0KbnljX2xpcXVvcl9saWNlbnNlcyAlPiUgZmlsdGVyKGlzLm5hKENpdHkpKSAlPiUgc2VsZWN0KG1vZF96aXApICU+JSBoZWFkDQpgYGANCkFjY29yZGluZyB0byB0aGUgdGFibGUgYWJvdmUsIG5vYm9keSBzaG91bGQgYmUgYWJsZSB0byBzZXJ2ZSBsaXF1b3IgaW4gdGhlIGZvbGxvd2luZyB6aXAgY29kZXMuIFVwb24gaW5zcGVjdGlvbiwgSSBmb3VuZCBhbiBleGFtcGxlLCBSZWRleWUgR3JpbGwgbG9jYXRlZCBpbiB6aXAgY29kZSAxMDEwNi4gaHR0cHM6Ly93d3cuem9tYXRvLmNvbS9uZXcteW9yay1jaXR5L3JlZGV5ZS1ncmlsbC1taWR0b3duL21lbnUNClRoaXMgcmVzdGF1cmFudCBzZWVtcyB0byBiZSBzZXJ2aW5nIGJsb29keSBtYXJ5J3MgYW5kIHByb3NlY2NvIGV2ZW4gdGhvdWdoIHRoaXMgaXMgbm90IGNvbmZpcm1lZCBieSB0aGUgZGF0YS4gVGhpcyBzaG93cyB0aGF0IHRoaXMgZGF0YXNldCBpcyBpbmNvbXBsZXRlLiBJIGRpZCBub3QgZG8gYSB0aG9yb3VnaCBjaGVjayBvZiBhbGwgcmVzdGF1cmFudHMgaW4gTmV3IFlvcmsgQ2l0eSwgYnV0IEkgd291bGQgZ3Vlc3MgdGhhdCBzZXZlcmFsIHJlc3RhdXJhbnRzIHNlcnZlIGFsY29ob2wgZXZlbiB0aG91Z2ggdGhleSBhcmVuJ3QgbGlzdGVkIGluIHRoZSBkYXRhc2V0LiBGb3IgdGhlIGNvbnRpbnVpbmcgYW5hbHlzaXMsIEkgd2lsbCBvbmx5IGZvY3VzIG9uIHRoZSB6aXAgY29kZXMgd2l0aCBjbGVhbiBtYXRjaGVzLiANCg0KYGBge3J9DQpueWNfbGlxdW9yX2xpY2Vuc2VzIDwtIG1lcmdlKG55X2xpcXVvcl9saWNlbnNlcywgemlwY29kZXMsIGJ5LnggPSAibW9kX3ppcCIsIGJ5LnkgPSAiWklQIikNCmBgYA0KDQpOZXh0LCBJIHdhbnQgdG8gbWFrZSBzdXJlIGFsbCBsYXRpdHVkZXMgYW5kIGxvbmdpdHVkZXMgYXJlIGluY2x1ZGVkDQpgYGB7cn0NCnN1bShpcy5uYShueWNfbGlxdW9yX2xpY2Vuc2VzJExhdGl0dWRlKSkNCmBgYA0KDQpUaGlzIG1pZ2h0IGNhdXNlIGlzc3VlcyBpbiBmdXJ0aGVyIGFuYWx5c2lzLiBIb3dldmVyIGJlY2F1c2Ugb2YgdGhlIG1lcmdlLCB3ZSBoYXZlIGFuIGVzdGltYXRlIGZvciB0aGUgbGF0aXR1ZGUgYW5kIGxvbmdpdHVkZSBmb3IgZWFjaCB6aXAgY29kZS4gSSB3aWxsIGltcHV0ZSB0aGVzZSBtaXNzaW5nIHZhbHVlcyB1c2luZyB0aGUgbGF0aXR1ZGUgYW5kIGxvbmdpdHVkZSBmcm9tIHRoZSBuZWlnaGJvcmhvb2QuDQoNCmBgYHtyfQ0KbnljX2xpcXVvcl9saWNlbnNlcyRMYXRpdHVkZVtpcy5uYShueWNfbGlxdW9yX2xpY2Vuc2VzJExhdGl0dWRlKV0gPC0gDQogIG55Y19saXF1b3JfbGljZW5zZXMkbGF0aXR1ZGVbaXMubmEobnljX2xpcXVvcl9saWNlbnNlcyRMYXRpdHVkZSldDQoNCg0KbnljX2xpcXVvcl9saWNlbnNlcyRMb25naXR1ZGVbaXMubmEobnljX2xpcXVvcl9saWNlbnNlcyRsb25naXR1ZGUpXSA8LSANCiAgbnljX2xpcXVvcl9saWNlbnNlcyRsb25naXR1ZGVbaXMubmEobnljX2xpcXVvcl9saWNlbnNlcyRMb25naXR1ZGUpXQ0KYGBgDQoNCkZpbmFsbHksIEkgd291bGQgbGlrZSB0byBjbGVhbiB1cCB0aGUgZGF0ZXMuIFRoZXkgYXJlIGluIHN0cmluZyBmb3JtYXQuIFVzaW5nIGx1YnJpZGF0ZSwgSSB3aWxsIGNvbnZlcnQgdGhlbSB0byBkYXRlcyBhbmQgYWRkIHNvbWUgY29sdW1ucyBjb3JyZXNwb25kaW5nIHRvIHRoZSB0aGUgeWVhci4gVGhpcyB3aWxsIG1ha2UgYW5hbHlzaXMgZWFzaWVyIHdpdGggdGhlIG90aGVyIGRhdGFzZXRzLg0KDQpgYGB7cn0NCm55Y19saXF1b3JfbGljZW5zZXMkTGljZW5zZS5PcmlnaW5hbC5Jc3N1ZS5EYXRlIDwtIG1keShueWNfbGlxdW9yX2xpY2Vuc2VzJExpY2Vuc2UuT3JpZ2luYWwuSXNzdWUuRGF0ZSkNCm55Y19saXF1b3JfbGljZW5zZXMkTGljZW5zZS5FZmZlY3RpdmUuRGF0ZSA8LSBtZHkobnljX2xpcXVvcl9saWNlbnNlcyRMaWNlbnNlLkVmZmVjdGl2ZS5EYXRlKQ0KbnljX2xpcXVvcl9saWNlbnNlcyRMaWNlbnNlLkV4cGlyYXRpb24uRGF0ZSA8LSBtZHkobnljX2xpcXVvcl9saWNlbnNlcyRMaWNlbnNlLkV4cGlyYXRpb24uRGF0ZSkNCm55Y19saXF1b3JfbGljZW5zZXMkaXNzdWVfeWVhciA8LSB5ZWFyKG55Y19saXF1b3JfbGljZW5zZXMkTGljZW5zZS5PcmlnaW5hbC5Jc3N1ZS5EYXRlKQ0KbnljX2xpcXVvcl9saWNlbnNlcyRlZmZlY3RpdmVfeWVhciA8LSB5ZWFyKG55Y19saXF1b3JfbGljZW5zZXMkTGljZW5zZS5FZmZlY3RpdmUuRGF0ZSkNCm55Y19saXF1b3JfbGljZW5zZXMkZXhwaXJhdGlvbl95ZWFyIDwtIHllYXIobnljX2xpcXVvcl9saWNlbnNlcyRMaWNlbnNlLkV4cGlyYXRpb24uRGF0ZSkNCmBgYA0KDQoNCiMjIDQuIEV4ZWN1dGl2ZSBTdW1tYXJ5IA0KDQpbTGluayB0byBFeGVjdXRpdmUgU3VtbWFyeV0oaHR0cHM6Ly9tYXJpa2Fsb2htdXMuc2hpbnlhcHBzLmlvL2V4ZWN1dGl2ZV9zdW1tYXJ5LykNCg0KIyMgNS4gTWFpbiBBbmFseXNpcyANCg0KIyMjIyBaaXAgQ29kZSwgTmVpZ2hib3Job29kIGFuZCBMYW5kIFZhbHVlIERhdGEgDQoNCk9uZSBvZiBvdXIgb3RoZXIgbWFpbiBxdWVzdGlvbnMgaW4gdGhpcyBwcm9qZWN0IHdhcyBsb29raW5nIGF0IGhvdyB0aGUgd2VhbHRoIHdhcyBkaXN0cmlidXRlZCBhbW9uZ3N0IGVhY2ggb2YgdGhlIHppcGNvZGVzIGluIHRoZSBmaXZlIGJvcm91Z2hzLiBVc2luZyB0aGUgY2Vuc3VzIGRhdGEgZnJvbSB0aGUgc3VwZXJ6aXAgZGF0YXNldCBhbmQgbWFwcGluZyB0aGF0IHRvIHRoZSB6aXBjb2RlIHNoYXBlZmlsZSBkb2N1bWVudCB3ZSBmb3VuZCwgd2UgbWFuYWdlZCB0byB2aXN1YWxpemUgdGhlIHdlYWx0aCBvZiBlYWNoIG5laWdoYm9yaG9vZC4gV2Ugd2lsbCBsYXRlciBiZSBhYmxlIHRvIHBsb3QgdGhlIGxpY2Vuc2UgYXBwbGljYXRpb25zIG92ZXIgdGhpcyBtYXBwaW5nIG9mIHRoZSB3ZWFsdGggZGlzdHJpYnV0aW9uLg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbWFwIDwtIGdldF9tYXAoIk5ldyBZb3JrIENpdHkiLCBzb3VyY2UgPSAiZ29vZ2xlIiwgbWFwdHlwZSA9ICJyb2FkbWFwIiwgem9vbSA9IDExLCBjb2xvcj0iYnciKQ0KDQpnZ21hcChtYXAsIGJhc2VfbGF5ZXIgPSBnZ3Bsb3QoYWVzKHggPSBsb25naXR1ZGUsIHkgPSBsYXRpdHVkZSwgY29sb3IgPSBhdmcpLCBkYXRhID0gY2Vuc3VzKSkgICsgZ2VvbV9wb2ludChzaXplID0gMywgYWxwaGE9MC43KSArIHNjYWxlX2NvbG9yX3ZpcmlkaXMoKSArIGdndGl0bGUoIkF2ZXJhZ2UgU2FsYXJ5IG9mIGVhY2ggWmlwY29kZSBpbiBOZXcgWW9yayIpDQpgYGANCg0KVGhpcyBtYXAgc2hvd3MgYWxsIG9mIHRoZSB6aXBjb2RlcyBhbmQgd2UgY2FuIGNsZWFybHkgc2VlIHRoYXQgYnkgZmFyIHRoZSBncmVhdGVzdCBpbmNvbWVzIHBlciB6aXBjb2RlIGFyZSBpbiB0aGUgVXBwZXIgRWFzdCBTaWRlIGFuZCBVcHBlciBXZXN0IFNpZGUuIFRoZXJlIGFwcGVhcnMgdG8gYmUgc29tZSBncmV5IGFyZWFhIGlmIHlvdSBsb29rIGNsb3NlbHkgYXQgdGhlIFVwcGVyIFdlc3QgU2lkZS4gVGhpcyBjb3JyZXNwb25kcyB0byBhYm91dCAkMTUwLDAwMC9wZXIgeWVhciBvbiB0aGUgY29sb3IgbWFwLiBUaGVuLCBtaWR0b3duIGFuZCBkb3dudG93biBhbHNvIGhhdmUgaGlnaCBpbmNvbWVzLiBBZnRlciB0aGF0LCB0aGV5IGFsbCBmYWxsIHRvd2FyZHMgdGhlIGJvdHRvbSBvZiB0aGUgc3BlY3RydW0uIFRodXMsIEkgdGhpbmsgaXQgd291bGQgYmUgdXNlZnVsIHRvIG1ha2UgYSBtYXAgc2hvd2luZyBvbmx5IE1hbmhhdHRhbiwgYW5kIHRoZW4gdGhlIGV2ZXJ5dGhpbmcgZXhjbHVkaW5nIE1hbmhhdHRhbi4gVGhpcyBzaG91bGQgZ2l2ZSB1cyBhIGJldHRlciBpZGVhIG9mIHdoYXQgYXJlYXMgb3V0c2lkZSBvZiBNYW5oYXR0YW4gbWlnaHQgc2VlIG1vcmUgYmFyL2NhZmUgb3BlbmluZ3MuDQoNCg0KYGBge3J9DQpjZW5zdXNfbWFuaGF0dGFuIDwtIGNlbnN1cyAlPiUgZHBseXI6OmZpbHRlcihjaXR5LnggPT0gJ01hbmhhdHRhbicpDQpnZ21hcChtYXAsIGJhc2VfbGF5ZXIgPSBnZ3Bsb3QoYWVzKHggPSBsb25naXR1ZGUsIHkgPSBsYXRpdHVkZSwgY29sb3IgPSBhdmcpLCBkYXRhID0gY2Vuc3VzX21hbmhhdHRhbikpICArIGdlb21fcG9pbnQoc2l6ZSA9IDQsIGFscGhhPTAuNykgKyBzY2FsZV9jb2xvcl92aXJpZGlzKCkgKyBnZ3RpdGxlKCJBdmVyYWdlIFNhbGFyeSBvZiBlYWNoIFppcGNvZGUgaW4gTWFuaGF0dGFuIikNCmBgYA0KDQpUaGlzIGdyYXBoIHNob3dzIGFsbCBvZiB0aGUgZGF0YSBmaWx0ZXJlZCBmb3IgTWFuaGF0dGFuIG9ubHkuIFdlIGNhbiBzZWUgdGhhdCBvbmNlIHdlIGdldCB0byBIYXJsZW0gYW5kIGFib3ZlLCB0aGUgYXZlcmFnZSBtZWRpYW4gaW5jb21lIHBlciBuZWlnaGJvcmhvb2QgZHJvcHMgaGVhdmlseS4gVGhlIHJlc3Qgb2YgdGhlIGNpdHkgaXMgcHJldHR5IHNpbWlsYXIuDQoNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmJyb29rbHluX21hcDIgPC0gZ2V0X21hcCgiQnJvb2tseW4sIE5ZIiwgIHNvdXJjZSA9ICJnb29nbGUiLCB6b29tID0gMTIsIG1hcHR5cGU9InJvYWRtYXAiLCBjb2xvcj0iYnciKQ0KY2Vuc3VzX2Jyb29rbHluIDwtIGNlbnN1cyAlPiUgZHBseXI6OiBmaWx0ZXIoY2l0eS54ID09ICdCcm9va2x5bicpDQpnZ21hcChicm9va2x5bl9tYXAyLCBiYXNlX2xheWVyID0gZ2dwbG90KGFlcyh4ID0gbG9uZ2l0dWRlLCB5ID0gbGF0aXR1ZGUsIGNvbG9yID0gYXZnKSwgZGF0YSA9IGNlbnN1c19icm9va2x5bikpICArIGdlb21fcG9pbnQoc2l6ZSA9IDQsIGFscGhhPTAuNykgKyBzY2FsZV9jb2xvcl92aXJpZGlzKCkgKyBnZ3RpdGxlKCJBdmVyYWdlIFNhbGFyeSBvZiBlYWNoIFppcGNvZGUgaW4gQnJvb2tseW4iKQ0KYGBgDQoNClRoaXMgc2hvd3MgdXMgdGhlIGF2ZXJhZ2Ugc2FsYXJ5IHRocm91Z2hvdXQgdGhlIHppcGNvZGVzIGluIEJyb29rbHluIGFuZCB3ZSBjYW4gc2VlIHRoYXQgaXQgaXMgaGlnaGVzdCBpbiBnZW5lcmFsIHdoZW4gdGhlIG5laWdoYm9yaG9vZCBpcyBjbG9zZXIgdG8gTWFuaGF0dGFuLg0KDQpBdCB0aGlzIHBvaW50LCBJIGhhZCByZWFsaXplZCB0aGF0IGl0IHdvdWxkIGJlIGhhcmQgdG8gbWFrZSBhIGNvbWJpbmVkIG1hcCBpbGx1c3RyYXRpbmcgYm90aCB0aGUgaW5jb21lIGRhdGEgYW5kIGxpY2Vuc2VzIGRhdGEgYnkgZGlzcGxheWluZyB0aGUgemlwcyBsaWtlIHRoaXMuIFRodXMsIEkgZm91bmQgemlwY29kZSBzaGFwZWZpbGUgZGF0YSBmb3IgTllDIGFuZCBpbXBvcnRlZCBpdCBpbiBvcmRlciB0byBtYWtlIGJldHRlciBtYXBzIHRvIGJlIHVzZWQgYXMgYSBiYXNlIGxheWVyIGZvciB0aGUgbGljZW5zaW5nIGRhdGEuIEkgYWxzbyBiZWd1biBsYWJlbGxpbmcgdGhlIG5laWdoYm9yaG9vZHMgdXNpbmcgdGhlIGxhYmVscyBNYXJpa2EgaGFkIGNyZWF0ZWQuDQoNCmBgYHtyfQ0KcGxvdHRpbmdfemlwczE8LXJlYWQuY3N2KCJEYXRhL05ZX3NoYXBlZmlsZXMuY3N2IikNCmBgYA0KDQoNCmBgYHtyfQ0KZ2dtYXAobWFwKSArIGdlb21fcG9seWdvbihhZXMoZmlsbCA9IGF2ZywgeCA9IGxvbmcsIHkgPSBsYXQsIGdyb3VwID0gZ3JvdXApLCBkYXRhID0gcGxvdHRpbmdfemlwczEsIGFscGhhID0gMC44KSArIGdndGl0bGUoIkRpc3RyaWJ1dGlvbiBvZiBBdmVyYWdlIFdlYWx0aCBwZXIgTmVpZ2hib3Job29kIGluIE5ldyBZb3JrIikgDQpgYGANCg0KSGVyZSB3ZSBzZWUgdGhhdCBNYW5oYXR0YW4gaGFzIHRoZSBoaWdoZXN0IHdlYWx0aC4gSXQgaGFzIHRoZSB3ZWFsdGhpZXN0IGFyZWFzIGJ1dCBhbHNvIHNvbWUgYXJlYXMgaW4gSGFybGVtIGFuZCBtb3JlIHVwdG93biB0aGF0IGFyZSBsZXNzIHdlYWx0aHkgdGhhbiBtb3N0IG5laWdoYm9yaG9vZHMgaW4gUXVlZW5zLCBCcm9va2x5biBhbmQgdGhlIEJyb254Lg0KDQpgYGB7cn0NCmdfYWxsIDwtIGdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKGZpbGwgPSBhdmcsIHggPSBsb25nLCB5ID0gbGF0LCBncm91cCA9IGdyb3VwKSwgZGF0YSA9IHBsb3R0aW5nX3ppcHMxLCBhbHBoYSA9IDAuOCkNCmdfYWxsICsgZ2d0aXRsZSgiRGlzdHJpYnV0aW9uIG9mIFdlYWx0aCBpbiBOZXcgWW9yayIpICsgc2NhbGVfZmlsbF92aXJpZGlzKCkNCmBgYA0KDQpIZXJlIHdlIHNlZSBhIGZ1bGwgZGlzdHJpYnV0aW9uIHVzaW5nIHRoZSB2aXJpZGlzIGNvbG9yIHNjaGVtZS4gVGhpcyB3aWxsIGJlIHVzZWQgYXMgdGhlIGJhc2UgZm9yIHRoZSBvdmVybGFpZCBwbG90cyBsYXRlciBvbiBhbmQgd2Ugd2lsbCB0aGVuIGZpbHRlciBieSBlYWNoIGJvcm91Z2guDQoNCiMjIyMjIFdlYWx0aCBieSBib3JvdWdoIC0gQWRhbQ0KDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpwbG90dGluZ196aXBzX21hbmhhdHRhbiA8LSBwbG90dGluZ196aXBzMSAlPiUgZmlsdGVyKGNpdHkueSA9PSAiTmV3IFlvcmsiKQ0KcGxvdHRpbmdfemlwc19icm9va2x5biA8LSBwbG90dGluZ196aXBzMSAlPiUgZmlsdGVyKGNpdHkueSA9PSAiQnJvb2tseW4iKQ0KcGxvdHRpbmdfemlwc19icm9ueCA8LSBwbG90dGluZ196aXBzMSAlPiUgZmlsdGVyKGNpdHkueSA9PSAiQnJvbngiKQ0KcGxvdHRpbmdfemlwc19xdWVlbnMgPC0gcGxvdHRpbmdfemlwczEgJT4lIGZpbHRlcighKGNpdHkueSAlaW4lIGMoIk5ldyBZb3JrIiwgIkJyb29rbHluIiwgIkJyb254IiwgIlN0YXRlbiBJc2xhbmQiKSkpDQoNCmJyb254X21hcCA8LSBnZXRfbWFwKCJCcm9ueCwgTlkiLCAgc291cmNlID0gImdvb2dsZSIsIHpvb20gPSAxMiwgbWFwdHlwZT0icm9hZG1hcCIsIGNvbG9yPSJidyIpDQpicm9va2x5bl9tYXAgPC0gZ2V0X21hcCgiQnJvb2tseW4sIE5ZIiwgIHNvdXJjZSA9ICJnb29nbGUiLCB6b29tID0gMTIsIG1hcHR5cGU9InJvYWRtYXAiLCBjb2xvcj0iYnciKSANCnF1ZWVuc19tYXAgPC0gZ2V0X21hcCgiQXN0b3JpYSwgTlkiLCAgc291cmNlID0gImdvb2dsZSIsIHpvb20gPSAxMiwgbWFwdHlwZT0icm9hZG1hcCIsIGNvbG9yPSJidyIpIA0KYGBgDQoNCldlIGFyZSBub3QgdXNpbmcgU3RhdGVuIElzbGFuZCBzaW5jZSB3ZSBoYXZlIG5vIHNpZGV3YWxrIGNhZmUgZGF0YSBmb3IgU3RhdGVuIElzbGFuZCB0byBjb21wYXJlIHRoZSB3ZWFsdGggdG8uDQoNCmBgYHtyfQ0KZ19tYW5oYXR0YW4gPC0gZ2dtYXAobWFwKSArIGdlb21fcG9seWdvbihhZXMoZmlsbCA9IGluY29tZS4sIHggPSBsb25nLCB5ID0gbGF0LCBncm91cCA9IGdyb3VwKSwgZGF0YSA9IHBsb3R0aW5nX3ppcHNfbWFuaGF0dGFuLCBhbHBoYSA9IDAuNSkNCmdfbWFuaGF0dGFuICsgZ2d0aXRsZSgiRGlzdHJpYnV0aW9uIG9mIFdlYWx0aCBpbiBNYW5oYXR0YW4iKSArIGdlb21fdGV4dF9yZXBlbChkYXRhPW1hbmhhdHRhbl9uYW1lcywgYWVzKG1lYW5fbG9uLCBtZWFuX2xhdCwgbGFiZWw9TmVpZ2hib3Job29kKSxmb250ZmFjZT0iYm9sZCIsIHNpemU9MykgKyBzY2FsZV9maWxsX3ZpcmlkaXMoKQ0KYGBgDQoNClRoaXMgbWFwIHNob3cgdXMgdGhlIHdlYWx0aCBpbiBNYW5oYXR0YW4gYWxvbmUgYnkgemlwY29kZS4gV2UgYXJlIG5vdyB1c2luZyB0aGUgdmlyaWRpcyBjb2xvciBzY2FsZSBzbyB0aGF0IGl0IGlzIHBlcmNlcHR1YWxseSB1bmlmb3JtIGFuZCBlYXNpZXIgdG8gc2VlIGRpZmZlcmVuY2VzIGluIGNvbG9yLiBUaGlzIGNsZWFybHkgc2hvd3MgdGhhdCB0aGUgaW5jb21lIGlzIHZlcnkgbG93IGF0IEhhcmxlbSBhbmQgYWJvdmUsIGluY2x1ZGluZyBNb3JuaW5nc2lkZSBIZWlnaHRzLiBUaGlzIGlzIGxpa2VseSBkdWUgdG8gdGhlIGZhY3QgdGhhdCBpdCBpcyBhbG1vc3QgZXhjbHVzaXZlbHkgc3R1ZGVudHMgbGl2aW5nIGluIE1vcm5pbmdzaWRlIEhlaWdodHMgYW5kIEhhcmxlbSBpcyBjaGVhcGVyLiBBbHNvLCBpdCBpcyBpbnRlcmVzdGluZyB0byBzZWUgdGhhdCBMb3dlciBFYXN0IFNpZGUgaGFzIGEgc2ltaWxhciBhdmVyYWdlIGluY29tZSB0byBIYXJsZW0uIFRoZSB3ZWFsdGhpZXN0IGFyZWFzIGFyZSBieSBmYXIgVXBwZXIgV2VzdCBTaWRlIGFuZCBVcHBlciBFYXN0IFNpZGUuIFdlIGNhbiBhbHNvIHNlZSB0aGF0IHdlIGFyZSBtaXNzaW5nIHdoYXQgc2VlbXMgdG8gYmUgZGF0YSBmb3IgdHdvIHppcGNvZGVzIGluIHRoZSBVcHBlciBFYXN0IHNpZGUuIFRoaXMgY29ycmVzcG9uZHMgdG8gc29tZSBvZiB0aGUgZGF0YSB0aGF0IE1hcmlrYSBoYWQgZm91bmQgd2FzIG1pc3NpbmcgYW5kIHN0aWxsIGRpZCBub3QgYXBwZWFyIGluIHRoZSBzdXBlcnppcCBkYXRhc2V0Lg0KDQpgYGB7ciBmaWd3aWR0aD0xMH0NCmdfYnJvb2tseW4gPC0gZ2dtYXAoYnJvb2tseW5fbWFwKSArIGdlb21fcG9seWdvbihhZXMoZmlsbCA9IGluY29tZS4sIHggPSBsb25nLCB5ID0gbGF0LCBncm91cCA9IGdyb3VwKSwgZGF0YSA9IHBsb3R0aW5nX3ppcHNfYnJvb2tseW4sIGFscGhhID0gMC41KQ0KZ19icm9va2x5biArIGdndGl0bGUoIkRpc3RyaWJ1dGlvbiBvZiBXZWFsdGggaW4gQnJvb2tseW4iKSArIGdlb21fdGV4dF9yZXBlbChkYXRhPWJyb29rbHluX25hbWVzLCBhZXMobWVhbl9sb24sIG1lYW5fbGF0LCBsYWJlbD1OZWlnaGJvcmhvb2QpLGZvbnRmYWNlPSJib2xkIiwgc2l6ZT0zKSArIHNjYWxlX2ZpbGxfdmlyaWRpcygpDQpgYGANCg0KQnkgb2JzZXJ2aW5nIHRoZSBpbmNvbWUgZGlzdHJpYnV0aW9uIGluIEJyb29rbHluLCB3ZSBzZWUgdGhhdCBpbiBnZW5lcmFsLCB0aGUgYXJlYXMgY2xvc2VyIHRvIE1hbmhhdHRhbiBoYXZlIHRoZSBoaWdoZXN0IGluY29tZSBzdWNoIGFzIEJyb29rbHluIEhlaWdodHMgYW5kIENyb3duIEhlaWdodHMuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpnX2Jyb254IDwtIGdnbWFwKGJyb254X21hcCkgKyBnZW9tX3BvbHlnb24oYWVzKGZpbGwgPSBpbmNvbWUuLCB4ID0gbG9uZywgeSA9IGxhdCwgZ3JvdXAgPSBncm91cCksIGRhdGEgPSBwbG90dGluZ196aXBzX2Jyb254LCBhbHBoYSA9IDAuNSkNCmdfYnJvbnggKyBnZ3RpdGxlKCJEaXN0cmlidXRpb24gb2YgV2VhbHRoIGluIEJyb254IikgKyBnZW9tX3RleHRfcmVwZWwoZGF0YT1icm9ueF9uYW1lcywgYWVzKG1lYW5fbG9uLCBtZWFuX2xhdCwgbGFiZWw9TmVpZ2hib3Job29kKSxmb250ZmFjZT0iYm9sZCIsIHNpemU9MykgKyBzY2FsZV9maWxsX3ZpcmlkaXMoKQ0KYGBgDQoNCg0KDQpgYGB7cn0NCmdfcXVlZW5zIDwtIGdnbWFwKHF1ZWVuc19tYXApICsgZ2VvbV9wb2x5Z29uKGFlcyhmaWxsID0gaW5jb21lLiwgeCA9IGxvbmcsIHkgPSBsYXQsIGdyb3VwID0gZ3JvdXApLCBkYXRhID0gcGxvdHRpbmdfemlwc19xdWVlbnMsIGFscGhhID0gMC41KQ0KZ19xdWVlbnMgKyBnZ3RpdGxlKCJEaXN0cmlidXRpb24gb2YgV2VhbHRoIGluIFF1ZWVucyIpICsgZ2VvbV90ZXh0X3JlcGVsKGRhdGE9cXVlZW5zX25hbWVzLCBhZXMobWVhbl9sb24sIG1lYW5fbGF0LCBsYWJlbD1OZWlnaGJvcmhvb2QpLGZvbnRmYWNlPSJib2xkIiwgc2l6ZT0zKSArIHNjYWxlX2ZpbGxfdmlyaWRpcygpDQpgYGANCg0KU3VycHJpc2luZ2x5LCBpdCBzZWVtcyB0aGF0IGluIFF1ZWVucywgc29tZSBvZiB0aGUgaGlnaGVyIGluY29tZSBhcmVhcyBhY3R1YWxseSBhcHBlYXIgZnVydGhlciBhd2F5IGZyb20gTWFuaGF0dGFuIGxpa2UgaW4gRmx1c2hpbmcgYW5kIEZvcmVzdCBIaWxscyBhbmQgUmVnbyBQYXJrLiBIb3dldmVyLCB0aGVyZSBhcmUgc29tZSBoaWdoZXIgaW5jb21lIGFyZWFzIGNsb3NlciB0byBNYW5oYXR0YW4gYXMgd2VsbCBsaWtlIEVhc3QgRWxtaHVyc3QuIA0KDQojIyMjIFNpZGV3YWxrIENhZmUgTGljZW5zZSBEYXRhIA0KQW55IGJ1c2luZXNzIHRoYXQgb3BlcmF0ZXMgYSBwb3J0aW9uIG9mIGEgcmVzdGF1cmFudCBvbiBhIHB1YmxpYyBzaWRld2FsayBtdXN0IG9idGFpbiBhIFNpZGV3YWxrIENhZmUgTGljZW5zZSBmcm9tIE5ldyBZb3JrIENpdHkuIFRoZXNlIGxpY2Vuc2VzIG11c3QgYmUgcmVuZXdlZCBldmVyeSB0d28geWVhcnMgYW5kIGZhbGwgaW50byB0aHJlZSBjYXRlZ29yaWVzOiBlbmNsb3NlZCwgdW5lbmNsb3NlZCwgb3Igc21hbGwgdW5lbmNsb3NlZCBzaWRld2FsayBjYWZlcy4gDQoNCiMjIyMjIEFuYWx5emluZyBieSBCb3JvdWdoDQoNCkZpcnN0LCB0byBoZWxwIGJldHRlciBvcmdhbml6ZSB0aGUgc2lkZXdhbGsgY2FmZSBsaWNlbnNlcyBieSBib3JvdWdoLCBJIGFkZGVkIGEgbmV3IGNvbHVtbiBjYWxsZWQgQk9ST1VHSCB0aGF0IGlzIHNldCB0byBNQU5IQVRUQU4sIEJST09LTFlOLCBCUk9OWCwgb3IgUVVFRU5TLiBJIGhhZCB0byBtYW51YWxseSBjaGVjayB0aGF0IG9ubHkgdGhlIGNpdGllcyBpbiBRdWVlbnMgaGFkIGJlZW4gY2FsbGVkIG91dCBzcGVjaWZpY2FsbHkgaW4gdGhlIENJVFkgY29sdW1uLCBzbyBpdCB3YXMgZWFzeSB0byBkaXN0aW5ndWlzaCB0aGVtIGZyb20gQlJPTlggb3IgQlJPT0tMWU4uDQpgYGB7cn0NCnNpZGV3YWxrcyA8LSBzaWRld2Fsa3MgJT4lIG11dGF0ZShCT1JPVUdIID0gaWZlbHNlKENJVFk9PSJORVcgWU9SSyJ8Q0lUWT09Ik5ldyBZb3JrIiwiTUFOSEFUVEFOIixpZmVsc2UoQ0lUWT09IkJST09LTFlOIiwiQlJPT0tMWU4iLGlmZWxzZShDSVRZPT0iQlJPTlgiLCJCUk9OWCIsIlFVRUVOUyIpKSkpDQpgYGANCg0KVG8gZ2V0IGEgYmV0dGVyIHVuZGVyc3RhbmRpbmcgb2YgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGVzZSBsaWNlbnNlcywgSSBoYXZlIHByb3ZpZGVkIGEgYmFyIGdyYXBoIGJ5IGJvcm91Z2guDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZ2dwbG90KHNpZGV3YWxrcywgYWVzKHg9ZmN0X2luZnJlcShCT1JPVUdIKSkpK2dlb21fYmFyKGFlcyhmaWxsPUJPUk9VR0gpKStnZ3RpdGxlKCJGcmVxdWVuY3kgb2YgU2lkZXdhbGsgQ2FmZSBMaWNlbnNlcyBieSBCb3JvdWdoIikreGxhYigiQm9yb3VnaCIpK3lsYWIoIkZyZXF1ZW5jeSIpK3RoZW1lX2ZpdmV0aGlydHllaWdodCgpDQpgYGANCkNsZWFybHkgTWFuaGF0dGFuIGhhcyB0aGUgbW9zdCBsaWNlbnNlIHJlcXVlc3RzLCBmb2xsb3dlZCBieSBCcm9va2x5biwgdGhlbiBRdWVlbnMgYW5kIGZpbmFsbHkgQnJvbnguIFNpbmNlIGF0IHRoZSBtb21lbnQgd2UgZG9uJ3QgaGF2ZSBuZWlnaGJvcmhvb2QgaW5mb3JtYXRpb24gKGV2ZXJ5dGhpbmcgaW4gTWFuaGF0dGFuIGlzIGp1c3QgY2xhc3NpZmllZCBhcyBOZXcgWW9yaywgQnJvb2tseW4gaGFzIG9ubHkgQnJvb2tseW4sIGFuZCBCcm9ueCBoYXMgb25seSB0aGUgY2l0eSBvZiBCcm9ueCksIHdlIGNhbiBvbmx5IGRpdmUgaW50byB0aGUgUXVlZW5zIGRhdGE6DQoNCmBgYHtyfQ0KcXVlZW5zX2NhZmVzIDwtIHNpZGV3YWxrcyAlPiUgZmlsdGVyKEJPUk9VR0g9PSJRVUVFTlMiKQ0KZ2dwbG90KHF1ZWVuc19jYWZlcywgYWVzKHg9ZmN0X2luZnJlcShDSVRZKSkpK2dlb21fYmFyKGZpbGw9InB1cnBsZSIpK2dndGl0bGUoIkZyZXF1ZW5jeSBvZiBTaWRld2FsayBDYWZlIExpY2Vuc2VzIGluIFF1ZWVucyIpK3hsYWIoIkNpdHkgLyBOZWlnaGJvcmhvb2QiKSt5bGFiKCJGcmVxdWVuY3kiKStjb29yZF9mbGlwKCkrdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkNCg0KYGBgDQoNCkluIFF1ZWVucywgYSBsYXJnZSBwZXJjZW50YWdlIG9mIGxpY2Vuc2UgcmVxdWVzdHMgY29tZSBmcm9tIEFzdG9yaWEsIGZvbGxvd2VkIGJ5IExvbmcgSXNsYW5kIENpdHkgYW5kIEZvcmVzdCBIaWxscy4gDQoNCk5leHQsIGluIG9yZGVyIHRvIGRvIGRhdGUgY29tcGFyaXNvbnMgdG8gYXNjZXJ0YWluIHdoaWNoIGFyZSB0aGUgbmV3IGFwcGxpY2F0aW9ucyB2cy4gcmVuZXdhbCBhcHBsaWNhdGlvbnMsIEkgaGFkIHRvIGNvbnZlcnQgY2VydGFpbiBkYXRlIGZpZWxkcyBmcm9tIHN0cmluZ3MgKHRoZXkgd2VyZSByZWFkIGluIGFzIHN0cmluZyBmYWN0b3JzKSBpbnRvIGRhdGVzLg0KDQpgYGB7cn0NCnNpZGV3YWxrcyRFWFBJUkFUSU9OX0RBVEU8LWFzLkRhdGUoc2lkZXdhbGtzJEVYUElSQVRJT05fREFURSwgZm9ybWF0PSIlbS8lZC8lWSIpDQpzaWRld2Fsa3MkQVBQX1NUQVRVU19EQVRFPC1hcy5EYXRlKHNpZGV3YWxrcyRBUFBfU1RBVFVTX0RBVEUsIGZvcm1hdD0iJW0vJWQvJVkiKQ0Kc2lkZXdhbGtzJFNVQk1JVF9EQVRFPC1hcy5EYXRlKHNpZGV3YWxrcyRTVUJNSVRfREFURSwgZm9ybWF0PSIlbS8lZC8lWSIpDQpgYGANCg0KIyMjIyMgTGljZW5zZXMgYnkgU3RhdHVzDQoNClRoZSBsaXN0IG9mIGxpY2Vuc2VzIGluY2x1ZGVzIGFjdGl2ZSBsaWNlbnNlcywgZXhwaXJlZCBsaWNlbnNlcywgbGljZW5zZXMgZm9yIGJ1c2luZXNzZXMgdGhhdCBoYXZlIGNsb3NlZCAoYW5kIGFyZSBub3cgaW5hY3RpdmUpLCBsaWNlbnNlcyB3aGljaCBhcmUgdXAgZm9yIHJlbmV3YWwgYXMgcGFydCBvZiB0aGUgdHdvIHllYXIgcHJvY2Vzcywgb3IgbmV3IHJlcXVlc3RzIGZvciBsaWNlbnNlcy4gVG8gYmV0dGVyIGNsYXNzaWZ5IHRoZW0sIEkgY3JlYXRlZCBhIG5ldyBmaWVsZCBjYWxsZWQgU1RBVFVTX0NMQVNTSUZJQ0FUSU9OLiBUaG9zZSBsaWNlbnNlcyB3aGljaCBhcmUgc3RpbGwgYWN0aXZlIGFuZCBub3QgdXAgZm9yIHJlbmV3YWwgYXJlIGNsYXNzaWZpZWQgYXMgIkFDVElWRSIuIFRob3NlIGxpY2Vuc2VzIHRoYXQgaGF2ZSBiZWVuIHN1Ym1pdHRlZCBmb3IgcmVuZXdhbCAoZWl0aGVyIGJlY2F1c2UgdGhlaXIgZXhwaXJhdGlvbiBkYXRlIGlzIGxlc3MgdGhhbiB0aGUgbGF0ZXN0IGFwcGxpY2F0aW9uIGRhdGEsIG9yIHRoYXQgYW4gYWN0aXZlIGxpY2Vuc2UgaXMgdXAgZm9yIHJldmlldykgYXJlIGNsYXNzaWZpZWQgYXMgIlJFTkVXQUwiLiBUaG9zZSBsaWNlbnNlcyB0aGF0IGFyZSBpbiB0aGUgc2hlZXQgYnV0IGRvIG5vdCBoYXZlIGEgbGljZW5zZSBudW1iZXIgYXJlIGNsYXNzaWZpZWQgYXMgIk5FVyIsIGFuZCB0aGUgcmVzdCBhcmUgbWFya2VkIGFzICJPTEQiIHRvIGVuY29tcGFzcyBpbmFjdGl2ZSBsaWNlbnNlcyB0aGF0IGhhdmUgbm90IGJlZW4gYWN0ZWQgdXBvbi4NCg0KYGBge3J9DQpzaWRld2Fsa3M8LXNpZGV3YWxrcyAlPiUgbXV0YXRlKFNUQVRVU19DTEFTU0lGSUNBVElPTiA9IGlmZWxzZShMSUNfU1RBVFVTPT0iQWN0aXZlIiAmIChBUFBfU1RBVFVTPT0iQXBwbGljYXRpb24gQXBwcm92ZWQiIHwgQVBQX1NUQVRVUz09IkFwcGxpY2F0aW9uIFJldmlldyBDb21wbGV0ZWQiKSwiQUNUSVZFIixpZmVsc2UoaXMubmEoTElDRU5TRV9OQlIpLCJORVciLGlmZWxzZSgoQVBQX1NUQVRVU19EQVRFPkVYUElSQVRJT05fREFURSB8IERQUUE9PSJJc3N1ZWQgVGVtcCBPcCBMZXR0ZXIiKSB8IChMSUNfU1RBVFVTPT0iQWN0aXZlIiAmIChBUFBfU1RBVFVTPT0iUGVuZGluZyBSZXZpZXciIHwgQVBQX1NUQVRVUz09IlN1Ym1pdHRlZCIpKSwiUkVORVdBTCIsIk9MRCIpKSkpDQpgYGANCg0KTm93IHRoYXQgd2UgaGF2ZSBjbGFzc2lmaWVkIHRoZSBzdGF0dXMgb2YgdGhlIGxpY2Vuc2VzLCB3ZSBhcmUgYWJsZSB0byBzZWUgaG93IHRoZXNlIGNsYXNzaWZpY2F0aW9ucyBkaWZmZXIgYmV0d2VlbiB0aGUgYm9yb3VnaHMuIA0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcid9DQpnZ3Bsb3Qoc2lkZXdhbGtzKStnZW9tX21vc2FpYyhhZXMoeD1wcm9kdWN0KFNUQVRVU19DTEFTU0lGSUNBVElPTixCT1JPVUdIKSxmaWxsPWZhY3RvcihTVEFUVVNfQ0xBU1NJRklDQVRJT04pKSkrY29vcmRfZmxpcCgpK2xhYnMoeD0iQm9yb3VnaCIseT0iTGljZW5zZSBTdGF0dXMiLCBmaWxsPSJMaWNlbnNlIERlc2lnbmF0aW9uIikrZ2d0aXRsZSgiQm9yb3VnaHMgYnkgTGljZW5zZSBTdGF0dXMiKSt0aGVtZV9maXZldGhpcnR5ZWlnaHQoKQ0KYGBgDQpUaGUgbW9zYWljIHBsb3Qgc2hvd3MgaG93IEJyb254IGFuZCBCcm9va2x5biBtYXkgYmUgZ2V0dGluZyBtb3JlIG5ldyBsaWNlbnNlIHJlcXVlc3RzIGFzIGEgcGVyY2VudGFnZSBvZiB0b3RhbCBsaWNlbnNlcy4gQnJvbnggaXMgYWxzbyBnZXR0aW5nIHRoZSBoaWdoZXN0IHBlcmNlbnRhZ2Ugb2YgcmVuZXdhbCByZXF1ZXN0cyBvdXQgb2YgaXRzIGluYWN0aXZlIGFuZCBhY3RpdmUgbGljZW5zZXMuIFdlIGNhbiBhbHNvIHRha2UgYSBsb29rIGF0IHRoZSBsaWNlbnNlIGRlc2lnbmF0aW9ucyBieSBib3JvdWdoOg0KDQpgYGB7cn0NCmdncGxvdChzaWRld2Fsa3MpK2dlb21fbW9zYWljKGFlcyh4PXByb2R1Y3QoQk9ST1VHSCxTVEFUVVNfQ0xBU1NJRklDQVRJT04pLGZpbGw9ZmFjdG9yKEJPUk9VR0gpKSkrY29vcmRfZmxpcCgpK2xhYnMoeD0iTGljZW5zZSBTdGF0dXMiLHk9IkJvcm91Z2giLCBmaWxsPSJCb3JvdWdoIikrZ2d0aXRsZSgiTGljZW5zZSBTdGF0dXMgYnkgQm9yb3VnaCIpK3RoZW1lX2ZpdmV0aGlydHllaWdodCgpDQpgYGANCg0KTG9va2luZyBhdCB0aGUgZGF0YSBpbiB0aGlzIHdheSwgeW91IGNhbiBzZWUgaG93IEJyb29rbHluIGhhcyB0aGUgc2Vjb25kLW1vc3QgbmV3IGxpY2Vuc2UgcmVxdWVzdHMsIGJ1dCBob3cgTWFuaGF0dGFuIHN0aWxsIGRvbWluYXRlcyBpbiBhbGwgbGljZW5zZSBzdGF0dXMgY2F0ZWdvcmllcy4NCg0KIyMjIyMgTWFwcGluZyBMaWNlbnNlcw0KDQpXZSBjYW4gbWFwIHRoZSBkYXRhIHRvIGhhdmUgYSBiZXR0ZXIgdmlldyBvZiB3aGVyZSB0aGUgZGF0YXBvaW50cyBsaWUuIFRvIGdldCBhbiBvdmVyYWxsIHBpY3R1cmUsIEkgc2VsZWN0ZWQgYSBtYXAgY2VudGVyZWQgb24gTG9uZyBJc2xhbmQgQ2l0eSBpbiBRdWVlbnMgc28gdGhhdCB3ZSBjYW4gZ2V0IGEgZ29vZCB2aWV3IG9mIGJvdGggQnJvb2tseW4gYW5kIEJyb254IGluIGFkZGl0aW9uIHRvIE1hbmhhdHRhbi4gDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbWFwIDwtIGdldF9tYXAoIGxvY2F0aW9uID0gYygtNzMuOTQ4NTQyNCwgNDAuNzQ1NDUxMyksICBzb3VyY2UgPSAiZ29vZ2xlIiwgem9vbSA9IDExLCBtYXB0eXBlPSJyb2FkbWFwIiwgY29sb3I9ImJ3IikgDQpgYGANClBsb3R0aW5nIGVhY2ggb2YgdGhlIHJlc3RhdXJhbnRzIGNvbG9yZWQgYnkgdGhlaXIgYm9yb3VnaC4gWW91IGNhbiBzZWUgaG93IE1hbmhhdHRhbiBkb21pbmF0ZXMgaW4gdGhlIG51bWJlciBvZiBzaWRld2FsayBjYWZlcywgYW5kIGhvdyB0aGUgc2lkZXdhbGsgY2FmZXMgaW4gQnJvb2tseW4gYW5kIFF1ZWVucyBhcmUgbGFyZ2VseSBjb25jZW50cmF0ZWQgaW4gdGhlIGFyZWFzIGNsb3NlciB0byBNYW5oYXR0YW4uDQpgYGB7ciBmaWcud2lkdGg9MTAsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpnZ21hcChtYXApK2dlb21fcG9pbnQoYWVzKHg9TE9OR0lUVURFLHk9TEFUSVRVREUsIGNvbG9yPUJPUk9VR0gpLGRhdGE9c2lkZXdhbGtzLCBhbHBoYT0wLjMpK2dndGl0bGUoIkRpc3RyaWJ1dGlvbiBvZiBhbGwgbGljZW5zZXMgKGluYWN0aXZlIG9yIGFjdGl2ZSkgaW4gTllDIikNCmBgYA0KRXZlbiB3aXRoIGFscGhhLCBpdCBpcyBkaWZmaWN1bHQgdG8gdGVsbCBleGFjdGx5IHdoZXJlIHRoZSBsYXJnZXN0IGNvbmNlbnRyYXRpb25zIG9mIHNpZGV3YWxrIGNhZmVzIGxpZS4gVXNpbmcgYSBkZW5zaXR5IHBsb3QsIHdlIGNhbiBiZXR0ZXIgc2VlIHRoZSBjb25jZW50cmF0aW9uIG9mIHNpZGV3YWxrIGNhZmVzIGFyb3VuZCBtaWQtdG8gbG93ZXIgTWFuaGF0dGFuLCBhbmQgdGhlIGNsdXN0ZXJzIGluIEFzdG9yaWEgYW5kIFdpbGxpYW1zYnVyZy4gDQpgYGB7ciBmaWcud2lkdGg9MTAsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpnIDwtIGdnbWFwKG1hcCkrc3RhdF9kZW5zaXR5MmQoYWVzKHg9TE9OR0lUVURFLHk9TEFUSVRVREUsIGZpbGw9Li5sZXZlbC4uKSxkYXRhPXNpZGV3YWxrcywgZ2VvbT0icG9seWdvbiIsIGFscGhhPTAuMikNCmcrc2NhbGVfZmlsbF9ncmFkaWVudChsb3c9InllbGxvdyIsaGlnaD0icmVkIikrZ2d0aXRsZSgiSGVhdG1hcCBvZiBTaWRld2FsayBDYWZlIExpY2Vuc2VzIGluIE5ZQyIpDQpgYGANClRoZSBuZXh0IHF1ZXN0aW9uIHdlIGNhbiBhc2sgaXMgd2hldGhlciB0aGVyZSBhcmUgY2xlYXIgcGF0dGVybnMgdG8gd2hlcmUgbmV3IGxpY2Vuc2UgcmVxdWVzdHMgYXJlIGNvbWluZyBpbiBmcm9tLCB3aGVyZSB0aGV5IGFyZSBiZWluZyByZW5ld2VkLCBvciB3aGVyZSB0aGV5IGhhdmUgZXhwaXJlZCB3aXRob3V0IHJlbmV3YWwuIA0KYGBge3IgZmlnLndpZHRoPTEwLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZ2dtYXAobWFwKStnZW9tX3BvaW50KGFlcyh4PUxPTkdJVFVERSx5PUxBVElUVURFLCBjb2xvcj1CT1JPVUdIKSxkYXRhPXNpZGV3YWxrcywgYWxwaGE9MC40KStmYWNldF93cmFwKH5TVEFUVVNfQ0xBU1NJRklDQVRJT04pK2dndGl0bGUoIlNpZGV3YWxrIENhZmUgTGljZW5zZXMgYnkgU3RhdHVzIikNCmBgYA0KSG93ZXZlciwgc2luY2Ugd2UgYXJlIHpvb21lZCBvdXQgYSBsb3QgYW5kIGxvb2tpbmcgYXQgdGhlIGVudGlyZSBzZXQgb2YgbGljZW5zZXMsIGl0IGlzIGRpZmZpY3VsdCB0byB0ZWxsIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIGRpc3RyaWJ1dGlvbnMgb2YgbGljZW5zZSByZXF1ZXN0cy4gRm9yIHRoaXMgYW5hbHlzaXMsIEkgYW0gb25seSBjb25jZXJuZWQgYWJvdXQgYWN0aXZlIGxpY2Vuc2VzICh3aGV0aGVyIHRoZXkgYXJlIGJlaW5nIHJlbmV3ZWQgb3Igbm90KSwgYW5kIG5ldyByZXF1ZXN0cyB0aGF0IGhhdmUgbm90IHlldCBiZWVuIGFwcHJvdmVkLiBUbyBkbyB0aGlzLCBJIGNyZWF0ZWQgYW5vdGhlciBTVEFUVVNfQ0xBU1NJRklDQVRJT04gdGhhdCBncm91cHMgYWN0aXZlIHJlbmV3YWwgcmVxdWVzdHMgaW50byB0aGUgQWN0aXZlIGNhdGVnb3J5LCBhbmQgc2V0cyBhbGwgb3RoZXIgbm9uLW5ldyByZXF1ZXN0cyBhcyAiaW5hY3RpdmUiLiANCmBgYHtyIGZpZy53aWR0aD0xMCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnNpZGV3YWxrcyA8LSBzaWRld2Fsa3MgJT4lIG11dGF0ZShTVEFUVVNfQ0xBU1NJRklDQVRJT04yPWlmZWxzZShTVEFUVVNfQ0xBU1NJRklDQVRJT049PSJBQ1RJVkUiIHwgKFNUQVRVU19DTEFTU0lGSUNBVElPTj09IlJFTkVXQUwiICYgTElDX1NUQVRVUz09IkFjdGl2ZSIpLCAiQUNUSVZFIiwgaWZlbHNlKFNUQVRVU19DTEFTU0lGSUNBVElPTj09Ik5FVyIsIk5FVyIsIk9MRCIpKSkNCm5ld19hY3RpdmUgPC0gc2lkZXdhbGtzICU+JSBmaWx0ZXIoU1RBVFVTX0NMQVNTSUZJQ0FUSU9OMj09IkFDVElWRSIgfCBTVEFUVVNfQ0xBU1NJRklDQVRJT04yPT0iTkVXIikNCmdnbWFwKG1hcCkrZ2VvbV9wb2ludChhZXMoeD1MT05HSVRVREUseT1MQVRJVFVERSwgY29sb3I9Qk9ST1VHSCksZGF0YT1uZXdfYWN0aXZlLCBhbHBoYT0wLjMpK2ZhY2V0X3dyYXAoflNUQVRVU19DTEFTU0lGSUNBVElPTjIpK2dndGl0bGUoIkFjdGl2ZSBhbmQgTmV3IExpY2Vuc2VzIGluIE5ldyBZb3JrIENpdHkiKQ0KYGBgDQpTaW5jZSB3ZSBhcmUgcXVpdGUgem9vbWVkIG91dCwgaXQgaXMgZGlmZmljdWx0IHRvIHNlZSB3aGF0IGV4YWN0bHkgaXMgaGFwcGVuaW5nIHdpdGggdGhlIE5ldyByZXF1ZXN0cy4gSG93ZXZlciwgaWYgd2Ugem9vbWVkIGluLCB3ZSB3b3VsZCBsb3NlIGluZm9ybWF0aW9uIGFib3V0IHRoZSBCcm9ueCBvciBsb3dlciBCcm9va2x5bi4gSW4gb3JkZXIgdG8gZGV0ZXJtaW5lIHdoZXRoZXIgdGhlcmUgaXMgYW55IHVzZWZ1bCBpbmZvcm1hdGlvbiB0aGVyZSwgSSBoYXZlIHpvb21lZCBpbiBvbiB0aGUgQnJvbnggcmVnaW9uLiANCg0KIyMjIyMgR2VvZ3JhcGhpYyBBbmFseXNpcyAtIEJyb254DQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KYnJvbnhfbWFwIDwtIGdldF9tYXAoIkJyb254LCBOWSIsICBzb3VyY2UgPSAiZ29vZ2xlIiwgem9vbSA9IDEyLCBtYXB0eXBlPSJyb2FkbWFwIiwgY29sb3I9ImJ3IikgDQpgYGANCmBgYHtyIGZpZy53aWR0aD0xMCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmdnbWFwKGJyb254X21hcCkrZ2VvbV9wb2ludChhZXMoeD1MT05HSVRVREUseT1MQVRJVFVERSwgY29sb3I9Qk9ST1VHSCksZGF0YT1uZXdfYWN0aXZlKStmYWNldF93cmFwKH5TVEFUVVNfQ0xBU1NJRklDQVRJT04yKStnZ3RpdGxlKCJBY3RpdmUgYW5kIE5ldyBMaWNlbnNlcyBpbiB0aGUgQnJvbngiKSt0aGVtZV9maXZldGhpcnR5ZWlnaHQoKQ0KYGBgDQpTaW5jZSB0aGVyZSBhcmUgdmVyeSBmZXcgYWN0aXZlIG9yIG5ldyBsaWNlbnNlcyBpbiB0aGUgQnJvbngsIEkgZmVlbCBjb21mb3J0YWJsZSBpbiB6b29taW5nIGluIG9uIHRoZSByZXN0IG9mIE1hbmhhdHRhbiAvIEJyb29rbHluIGFuZCBRdWVlbnMgaW4gb3JkZXIgdG8gYmUgYWJsZSB0byBiZXR0ZXIgc2VlIHdoYXQgaXMgaGFwcGVuaW5nIGF0IHRoZSBleHBlbnNlIG9mIEJyb254LiANCg0KIyMjIyMgR2VvZ3JhcGhpYyBBbmFseXNpcyAtIE1hbmhhdHRhbg0KSSB3YW50IHRvIHRha2UgYSBjbG9zZXIgbG9vayBhdCB3aGF0IGlzIGhhcHBlbmluZyBpbiBNYW5oYXR0YW4uIEluIG9yZGVyIHRvIGRvIHRoaXMgYXQgYSBtb3JlIGdyYW51bGFyIGxldmVsLCBJIHdpbGwgdXNlIG15IG1lcmdlZCBkYXRhIHdpdGggb3VyIG1hc3Rlcl96aXAgZG9jdW1lbnQgd2hpY2ggbGlzdHMgdGhlIG5laWdoYm9yaG9vZHMgb2YgZWFjaCBCb3JvdWdoIGJ5IHppcCBjb2RlLiBJIGFsc28gcHVsbGVkIG91dCB0aGUgeWVhciBvZiB0aGUgc3VibWlzc2lvbiBvZiB0aGUgbGljZW5zZSBhcHBsaWNhdGlvbiBhbmQgc2F2ZWQgaXQgYXMgU1VCTUlUX1lFQVIuIA0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCm1hc3Rlcl96aXA8LXJlYWQuY3N2KCJEYXRhL3ppcHNfbWFzdGVyX25vX21pc3NpbmdfbmJyaC5jc3YiLCBzdHJpcC53aGl0ZT1UUlVFKQ0Kc2lkZXdhbGtzX25iaCA8LSBtZXJnZShzaWRld2Fsa3MsIG1hc3Rlcl96aXAsIGJ5PSJaSVAiLCBhbGwueD1UUlVFKQ0KDQpzaWRld2Fsa3NfbmJoIDwtIHNpZGV3YWxrc19uYmggJT4lIG11dGF0ZShTVUJNSVRfWUVBUj15ZWFyKFNVQk1JVF9EQVRFKSkNCg0Kc2lkZXdhbGtzX21hbmhhdHRhbl9hY3RpdmUgPC0gc2lkZXdhbGtzX25iaCAlPiUgZmlsdGVyKEJPUk9VR0g9PSJNQU5IQVRUQU4iICYgKFNUQVRVU19DTEFTU0lGSUNBVElPTjI9PSJBQ1RJVkUiIHwgU1RBVFVTX0NMQVNTSUZJQ0FUSU9OMj09Ik5FVyIpKQ0KDQptYW5oYXR0YW5fbWFwIDwtIGdldF9tYXAoIk1hbmhhdHRhbiwgTlkiLCBzb3VyY2U9Imdvb2dsZSIsIG1hcHR5cGU9InJvYWRtYXAiLCB6b29tPTEyLCBjb2xvcj0iYnciKQ0KYGBgDQpJIGhhdmUgYWxzbyBjYWxjdWxhdGVkIHRoZSBhdmVyYWdlIGxvY2F0aW9ucyBvZiB0aGUgbmVpZ2hib3Job29kcyBpbiBlYWNoIGJvcm91Z2ggYmFzZWQgb24gdGhlaXIgYXNzaWduZWQgWmlwIGNvZGVzLiANCmBgYHtyIGZpZy53aWR0aD0xMCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCg0KZ2dtYXAobWFuaGF0dGFuX21hcCkrZ2VvbV9wb2ludChhZXMoeD1MT05HSVRVREUsIHk9TEFUSVRVREUsIGNvbG9yPW5iaCksIGRhdGE9c2lkZXdhbGtzX21hbmhhdHRhbl9hY3RpdmUpK2dndGl0bGUoIkFsbCBhY3RpdmUgb3IgbmV3IHNpZGV3YWxrIGNhZmVzIGluIE1hbmhhdHRhbiIpDQoNCnNpZGV3YWxrc19icm9ueDwtIHNpZGV3YWxrc19uYmggJT4lIGZpbHRlcihCT1JPVUdIPT0iQlJPTlgiKQ0KDQpgYGANClRoaXMgdmlldyBzaG93cyBhbGwgb2YgdGhlIGRpc3RyaWJ1dGlvbiBvZiBhbGwgY3VycmVudGx5IGFjdGl2ZSBvciBuZXcgc2lkZXdhbGsgY2FmZXMgaW4gTWFuaGF0dGFuLiBUbyBnZXQgYSBiZXR0ZXIgaWRlYSBvZiBkZW5zaXR5LCB3ZSBjYW4gYWxzbyBsb29rIGF0IGEgaGVhdG1hcCBvZiB0aGlzIHZpZXcuIA0KDQpgYGB7ciBmaWcud2lkdGg9MTAsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpnIDwtIGdnbWFwKG1hbmhhdHRhbl9tYXApK3N0YXRfZGVuc2l0eTJkKGFlcyh4PUxPTkdJVFVERSx5PUxBVElUVURFLCBmaWxsPS4ubGV2ZWwuLiksZGF0YT1zaWRld2Fsa3NfbWFuaGF0dGFuX2FjdGl2ZSwgZ2VvbT0icG9seWdvbiIsIGFscGhhPTAuMykNCmcrc2NhbGVfZmlsbF9ncmFkaWVudChsb3c9InllbGxvdyIsaGlnaD0icmVkIikrZ2d0aXRsZSgiSGVhdG1hcCBvZiBTaWRld2FsayBDYWZlIExpY2Vuc2VzIGluIE1hbmhhdHRhbiIpK2dlb21fdGV4dF9yZXBlbChkYXRhPW1hbmhhdHRhbl9uYW1lcywgYWVzKG1lYW5fbG9uLCBtZWFuX2xhdCwgbGFiZWw9TmVpZ2hib3Job29kKSxmb250ZmFjZT0iYm9sZCIsIHNpemU9MykNCg0KYGBgDQoNClRoaXMgaGVhdG1hcCBzaG93cyB0aGUgaGlnaGVzdCBjb25jZW50cmF0aW9uIG9mIHNpZGV3YWxrIGNhZmVzIGluIEdyZWVud2ljaCBWaWxsYWdlIGFuZCBOb0hvLiBUbyBzZWUgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBhY3RpdmUgYW5kIG5ldyBsaWNlbnNlcywgd2UgY2FuIGZhY2V0IGJhc2VkIG9uIG91ciBwcmV2aW91c2x5IGNhbGN1bGF0ZWQgY2F0ZWdvcml6YXRpb24uDQoNCmBgYHtyIGZpZy53aWR0aD0xMCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmcgPC0gZ2dtYXAobWFuaGF0dGFuX21hcCkrc3RhdF9kZW5zaXR5MmQoYWVzKHg9TE9OR0lUVURFLHk9TEFUSVRVREUsIGZpbGw9Li5sZXZlbC4uKSxkYXRhPXNpZGV3YWxrc19tYW5oYXR0YW5fYWN0aXZlLCBnZW9tPSJwb2x5Z29uIiwgYWxwaGE9MC4zKStmYWNldF93cmFwKH5TVEFUVVNfQ0xBU1NJRklDQVRJT04yKQ0KZytzY2FsZV9maWxsX2dyYWRpZW50KGxvdz0ieWVsbG93IixoaWdoPSJyZWQiKStnZ3RpdGxlKCJIZWF0bWFwIG9mIFNpZGV3YWxrIENhZmUgTGljZW5zZXMgaW4gTWFuaGF0dGFuIikrZ2VvbV90ZXh0X3JlcGVsKGRhdGE9bWFuaGF0dGFuX25hbWVzLCBhZXMobWVhbl9sb24sIG1lYW5fbGF0LCBsYWJlbD1OZWlnaGJvcmhvb2QpLGZvbnRmYWNlPSJib2xkIiwgc2l6ZT0zKSt0aGVtZV9maXZldGhpcnR5ZWlnaHQoKStndWlkZXMoZmlsbD1GQUxTRSkNCmBgYA0KVGhlIGhlYXRtYXAgdmlldyBvZiB0aGUgbWFwIGlzIG5vdCBhIGdyZWF0IG9uZSAtIGl0IGlzIGRpZmZpY3VsdCB0byB0ZWxsIHRoZSBkaWZmZXJlbmNlcyBvbiB0aGUgc2FtZSBzY2FsZXMgaW4gYSBoZWF0bWFwLiBMb29raW5nIGF0IHRoZSBzYW1lIHZpZXcsIGJ1dCBieSB1c2luZyBnZW9tX3BvaW50IGFnYWluLCB3ZSBnZXQgdGhlIGZvbGxvd2luZyB2aWV3OiANCmBgYHtyIGZpZy53aWR0aD0xMCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmdnbWFwKG1hbmhhdHRhbl9tYXApK2dlb21fcG9pbnQoYWVzKHg9TE9OR0lUVURFLHk9TEFUSVRVREUsIGNvbG9yPW5iaCksZGF0YT1zaWRld2Fsa3NfbWFuaGF0dGFuX2FjdGl2ZSkrZmFjZXRfd3JhcCh+U1RBVFVTX0NMQVNTSUZJQ0FUSU9OMikrZ2d0aXRsZSgiQWN0aXZlIGFuZCBOZXcgTGljZW5zZXMgaW4gTWFuaGF0dGFuIikgKyBndWlkZXMoY29sb3I9RkFMU0UpK2dlb21fdGV4dF9yZXBlbChkYXRhPW1hbmhhdHRhbl9uYW1lcywgYWVzKG1lYW5fbG9uLCBtZWFuX2xhdCwgbGFiZWw9TmVpZ2hib3Job29kKSxmb250ZmFjZT0iYm9sZCIsIHNpemU9MykrdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkNCmBgYA0KT25jZSBhZ2FpbiwgdGhlIGRhdGEgaXMgbm90IHRlbGxpbmcgdXMgdG9vIG11Y2ggc2luY2UgdGhlcmUgYXJlIHZlcnkgZmV3IG5ldyByZXF1ZXN0cy4gUGVyaGFwcyBhIGJldHRlciB3YXkgdG8gY2F0ZWdvcml6ZSB0aGlzIGRhdGEgaXMgdG8gbG9vayBhdCB0aGUgeWVhcnMgdGhhdCBhcHBsaWNhdGlvbnMgd2VyZSBzdWJtaXR0ZWQsIHRoZXJlZm9yZSBpZ25vcmluZyB3aGV0aGVyIGEgbGljZW5zZSBpcyBjdXJyZW50bHkgYWN0aXZlIG9yIG5vdC4NCg0KYGBge3J9DQp5ZWFyX2J4cDwtZ2dwbG90KHNpZGV3YWxrc19uYmgsIGFlcyh5PVNVQk1JVF9ZRUFSLHg9MSkpK2dlb21fYm94cGxvdCgpK2dndGl0bGUoIkJveHBsb3Qgb2YgU3VibWlzc2lvbiBZZWFycyIpK3RoZW1lX2ZpdmV0aGlydHllaWdodCgpDQp5ZWFyX2JhcjwtZ2dwbG90KHNpZGV3YWxrc19uYmgsYWVzKHg9U1VCTUlUX1lFQVIpKStnZW9tX2JhcigpK2dndGl0bGUoIkJhciBHcmFwaCBvZiBTdWJtaXNzaW9uIFllYXJzIikrdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkNCmdyaWQuYXJyYW5nZSh5ZWFyX2J4cCwgeWVhcl9iYXIsIG5jb2w9MikNCmBgYA0KDQpTaW5jZSB0aGVyZSBhcmUgdmVyeSBmZXcgc3VibWlzc2lvbnMgYmV0d2VlbiAyMDAwIGFuZCAyMDE0LCBJIHdpbGwgYmUgcmVtb3ZpbmcgdGhlc2Ugb3V0bGllcnMgYXMgcG90ZW50aWFsIG1pc3Rha2VzIHdoZXJlIHRoZSBzdWJtaXQgZGF0ZXMgd2VyZSBub3QgdXBkYXRlZCBvbmNlIHRoZSBsaWNlbnNlcyB3ZXJlIHJlbmV3ZWQuIE5vdyB3ZSBjYW4gbG9vayBhdCBhIG1vc2FpYyBwbG90IG9mIHRoZSBkaWZmZXJlbnQgbmVpZ2hib3Job29kcyBhbmQgdGhlIHllYXJzIHRoYXQgdGhlIGxpY2Vuc2VzIHdlcmUgcmVxdWVzdGVkLg0KDQpgYGB7cn0NCnNpZGV3YWxrc19uYmg8LSBzaWRld2Fsa3NfbmJoICU+JSBmaWx0ZXIoU1VCTUlUX1lFQVI+MjAxNCkNCg0Kc2lkZXdhbGtzX21hbmhhdHRhbjwtIHNpZGV3YWxrc19uYmggJT4lIGZpbHRlcihCT1JPVUdIPT0iTUFOSEFUVEFOIikNCg0KbWFuaGF0dGFuX2NvdW50cyA8LSBzaWRld2Fsa3NfbWFuaGF0dGFuICU+JSBncm91cF9ieShuYmgsIFNVQk1JVF9ZRUFSKSAlPiUgc3VtbWFyaXNlKGNvdW50PW4oKSkNCg0KbWFuaGF0dGFuX2NvdW50cyRTVUJNSVRfWUVBUjwtYXMuZmFjdG9yKG1hbmhhdHRhbl9jb3VudHMkU1VCTUlUX1lFQVIpDQoNCm1hbmhhdHRhbl9jb3VudHMkU1VCTUlUX1lFQVI8LWZhY3RvcihtYW5oYXR0YW5fY291bnRzJFNVQk1JVF9ZRUFSLCBjKCIyMDE3IiwgIjIwMTYiLCIyMDE1IikpDQoNCmdncGxvdChtYW5oYXR0YW5fY291bnRzLCBhZXMoeD1yZW9yZGVyKG5iaCwgY291bnQpLCB5PWNvdW50LCBmaWxsPVNVQk1JVF9ZRUFSKSkrZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiLHBvc2l0aW9uPXBvc2l0aW9uX2RvZGdlKCkpK2Nvb3JkX2ZsaXAoKSt4bGFiKCJOZWlnaGJvcmhvb2QiKStnZ3RpdGxlKCJMaWNlbnNlIFJlcXVlc3RzIHBlciBOZWlnaGJvcmhvb2QgYW5kIFllYXIiKSt0aGVtZV9maXZldGhpcnR5ZWlnaHQoKQ0KYGBgDQpJdCBtYWtlcyBzZW5zZSB0aGF0IDIwMTYgd291bGQgaGF2ZSBtb3JlIGFwcGxpY2F0aW9ucyB0aGFuIDIwMTUsIHNpbmNlIG1hbnkgb2YgdGhlIDIwMTUgYXBwbGljYXRpb25zIGhhdmUgYmVlbiByZW5ld2VkIGJ5IG5vdy4gSXQgbG9va3MgbGlrZSBHcmVlbndpY2ggVmlsbGFnZSwgVXBwZXIgV2VzdCBTaWRlLCBhbmQgVXBwZXIgRWFzeSBTaWRlIGNvbnNpc3RlbnRseSBoYXZlIHRoZSBoaWdoZXN0IG51bWJlciBvZiByZXF1ZXN0cyB0aHJvdWdob3V0IHRoZSB0aHJlZSB5ZWFycy4gSG93ZXZlciwgdG8gZ2V0IGEgYmV0dGVyIHVuZGVyc3RhbmRpbmcgb2YgdGhlIGRpc3RyaWJ1dGlvbiBvZiBhcmVhcywgd2UgY2FuIGNyZWF0ZSB0aHJlZSBzZXBhcmF0ZSBoZWF0bWFwcy4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQptYXAyMDE1IDwtIGdnbWFwKG1hbmhhdHRhbl9tYXApK3N0YXRfZGVuc2l0eTJkKGFlcyh4PUxPTkdJVFVERSx5PUxBVElUVURFLCBmaWxsPS4ubGV2ZWwuLiksZGF0YT1zaWRld2Fsa3NfbWFuaGF0dGFuJT4lZmlsdGVyKFNVQk1JVF9ZRUFSPT0iMjAxNSIpLCBnZW9tPSJwb2x5Z29uIiwgYWxwaGE9MC4zKStzY2FsZV9maWxsX2dyYWRpZW50KGxvdz0ieWVsbG93IixoaWdoPSJyZWQiKStnZ3RpdGxlKCJNYW5oYXR0YW4gTGljZW5zZXMgQXBwbGllZCBmb3IgaW4gMjAxNSIpK2dlb21fdGV4dF9yZXBlbChkYXRhPW1hbmhhdHRhbl9uYW1lcywgYWVzKG1lYW5fbG9uLCBtZWFuX2xhdCwgbGFiZWw9TmVpZ2hib3Job29kKSxmb250ZmFjZT0iYm9sZCIsIHNpemU9MykNCm1hcDIwMTUNCmBgYA0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCm1hcDIwMTY8LWdnbWFwKG1hbmhhdHRhbl9tYXApK3N0YXRfZGVuc2l0eTJkKGFlcyh4PUxPTkdJVFVERSx5PUxBVElUVURFLCBmaWxsPS4ubGV2ZWwuLiksZGF0YT1zaWRld2Fsa3NfbWFuaGF0dGFuJT4lZmlsdGVyKFNVQk1JVF9ZRUFSPT0iMjAxNiIpLCBnZW9tPSJwb2x5Z29uIiwgYWxwaGE9MC4zKStzY2FsZV9maWxsX2dyYWRpZW50KGxvdz0ieWVsbG93IixoaWdoPSJyZWQiKStnZ3RpdGxlKCJNYW5oYXR0YW4gTGljZW5zZXMgQXBwbGllZCBmb3IgaW4gMjAxNiIpK2dlb21fdGV4dF9yZXBlbChkYXRhPW1hbmhhdHRhbl9uYW1lcywgYWVzKG1lYW5fbG9uLCBtZWFuX2xhdCwgbGFiZWw9TmVpZ2hib3Job29kKSxmb250ZmFjZT0iYm9sZCIsIHNpemU9MykrZ3VpZGVzKGZpbGw9RkFMU0UpDQptYXAyMDE2DQpgYGANCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCm1hcDIwMTcgPC0gZ2dtYXAobWFuaGF0dGFuX21hcCkrc3RhdF9kZW5zaXR5MmQoYWVzKHg9TE9OR0lUVURFLHk9TEFUSVRVREUsIGZpbGw9Li5sZXZlbC4uKSxkYXRhPXNpZGV3YWxrc19tYW5oYXR0YW4lPiVmaWx0ZXIoU1VCTUlUX1lFQVI9PSIyMDE3IiksIGdlb209InBvbHlnb24iLCBhbHBoYT0wLjMpK3NjYWxlX2ZpbGxfZ3JhZGllbnQobG93PSJ5ZWxsb3ciLGhpZ2g9InJlZCIpK2dndGl0bGUoIk1hbmhhdHRhbiBMaWNlbnNlcyBBcHBsaWVkIGZvciBpbiAyMDE3IikrZ2VvbV90ZXh0X3JlcGVsKGRhdGE9bWFuaGF0dGFuX25hbWVzLCBhZXMobWVhbl9sb24sIG1lYW5fbGF0LCBsYWJlbD1OZWlnaGJvcmhvb2QpLGZvbnRmYWNlPSJib2xkIiwgc2l6ZT0zKStndWlkZXMoZmlsbD1GQUxTRSkNCm1hcDIwMTcNCg0KYGBgDQpBY3Jvc3MgYWxsIHRocmVlIHllYXJzLCB5b3UgY2FuIHNlZSBob3cgdGhlIGNvbmNlbnRyYXRlZCBhcmVhIG9mIGxpY2Vuc2VzIHJlbWFpbnMgaW4gdGhlIEdyZWVud2ljaCBWaWxsYWdlIGFyZWEuIEhvd2V2ZXIsIGl0IGxvb2tzIGxpa2UgdGhlIGNvbmNlbnRyYXRpb24gb2YgYXBwbGljYW50cyBpbiB0aGUgVXBwZXIgV2VzdCBTaWRlIGluIDIwMTcgaGFzIGRyb3BwZWQuIFRoaXMgbWFwIGlzIG1vcmUgdXNlZnVsIHRoYW4gdGhlIGJhciBjaGFydCBiZWNhdXNlIGl0IHNob3dzIHRoZSBsb2NhdGlvbiBvZiB0aGUgY2FmZXMgdGhhdCBtYXkgYmUgc3RyYWRkbGluZyB0d28gbmVpZ2hib3Job29kcyAtIGluZm9ybWF0aW9uIHRoYXQgaXMgbm90IGFwcGFyZW50IGZyb20gdGhlIGdyb3VwZWQgYmFyIGNoYXJ0LiANCg0KV2UgY2FuIGRvIHRoZSBzYW1lIHNvcnQgb2YgYW5hbHlzaXMgaW4gYWxsIGZvdXIgYm9yb3VnaHMgdGhhdCB3ZSBoYXZlIHNpZGV3YWxrIGNhZmUgaW5mb3JtYXRpb24gYWJvdXQuIFRvIG1ha2UgdGhpcyBhbmFseXNpcyBlYXNpZXIgdG8gdmlldywgSSBoYXZlIGNyZWF0ZWQgYSBzaGlueSBhcHAgd2hpY2ggY2FuIGJlIGZvdW5kIGJ5IGZvbGxvd2luZyB0aGlzIGxpbms6IFtTaGlueUNhZmVdKGh0dHBzOi8vbWFyaWthbG9obXVzLnNoaW55YXBwcy5pby9jYWZlc2hpbnkvKQ0KDQpgYGB7ciwgIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpzaWRld2Fsa3NfYnJvb2tseW4gPC0gc2lkZXdhbGtzX25iaCAlPiUgZmlsdGVyKEJPUk9VR0g9PSJCUk9PS0xZTiIpDQpzaWRld2Fsa3NfcXVlZW5zIDwtIHNpZGV3YWxrc19uYmggJT4lIGZpbHRlcihCT1JPVUdIPT0iUVVFRU5TIikNCnNpZGV3YWxrc19icm9ueCA8LSBzaWRld2Fsa3NfbmJoICU+JSBmaWx0ZXIoQk9ST1VHSD09IkJST05YIikNCg0KYnJvb2tseW5fbWFwIDwtIGdldF9tYXAoIkJyb29rbHluLCBOWSIsICBzb3VyY2UgPSAiZ29vZ2xlIiwgem9vbSA9IDEyLCBtYXB0eXBlPSJyb2FkbWFwIiwgY29sb3I9ImJ3IikgDQpxdWVlbnNfbWFwIDwtIGdldF9tYXAoIkFzdG9yaWEsIE5ZIiwgIHNvdXJjZSA9ICJnb29nbGUiLCB6b29tID0gMTIsIG1hcHR5cGU9InJvYWRtYXAiLCBjb2xvcj0iYnciKSANCmBgYA0KVGhlIFNoaW55IGFwcCBoYXMgYSBicmllZiBibHVyYiBleHBsYWluaW5nIHdoYXQgZWFjaCBib3JvdWdoIGNhbiB0ZWxsIHVzIGFib3V0IHRoZSBtb3ZlbWVudCBvZiBzaWRld2FsayBjYWZlIGFwcGxpY2F0aW9ucy4NCg0KTmV4dCwgSSBhZGRlZCBBZGFtJ3MgaW5jb21lIGRpc3RyaWJ1dGlvbiBwbG95Z29ucyB0byB0aGUgYmFja2dyb3VuZCBvZiB0aGVzZSBtYXBzIHRvIHNlZSB3aGF0IHRoZSByZWxhdGlvbnNoaXAgaXMgdG8gaW5jb21lLiANCg0KYGBge3IgZmlnLndpZHRoPTEwLCAgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmdnbWFwKG1hbmhhdHRhbl9tYXApK2dlb21fcG9seWdvbihhZXMoZmlsbCA9IGluY29tZS4sIHggPSBsb25nLCB5ID0gbGF0LCBncm91cCA9IGdyb3VwKSwgZGF0YSA9IHBsb3R0aW5nX3ppcHNfbWFuaGF0dGFuLCBhbHBoYSA9IDAuNykrZ2VvbV9wb2ludChhZXMoeD1MT05HSVRVREUseT1MQVRJVFVERSwgY29sb3I9InB1cnBsZSIpLGRhdGE9c2lkZXdhbGtzX21hbmhhdHRhbiwgc2l6ZT0xKSsgc2NhbGVfZmlsbF92aXJpZGlzKCkrZ2VvbV90ZXh0X3JlcGVsKGRhdGE9bWFuaGF0dGFuX25hbWVzLCBhZXMobWVhbl9sb24sIG1lYW5fbGF0LCBsYWJlbD1OZWlnaGJvcmhvb2QpLGZvbnRmYWNlPSJib2xkIiwgc2l6ZT0zKStndWlkZXMoY29sb3I9RikNCmBgYA0KTWFuaGF0dGFuIGhhcyBhIHByZXR0eSBjbGVhciBjbHVzdGVyaW5nIG9mIHNpZGV3YWxrIGNhZmVzIGluIHRoZSBoaWdoZXIgaW5jb21lIGFyZWFzLCB3aXRoIHRoZSBleGNlcHRpb24gb2YgTG93ZXIgTWFoYXR0YW4gYW5kIHRoZSBGaW5hbmNpYWwgRGlzdHJpY3QuDQoNCmBgYHtyIGZpZy53aWR0aD0xMCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmdnbWFwKGJyb29rbHluX21hcCkrZ2VvbV9wb2x5Z29uKGFlcyhmaWxsID0gaW5jb21lLiwgeCA9IGxvbmcsIHkgPSBsYXQsIGdyb3VwID0gZ3JvdXApLCBkYXRhID0gcGxvdHRpbmdfemlwc19icm9va2x5biwgYWxwaGEgPSAwLjcpK2dlb21fcG9pbnQoYWVzKHg9TE9OR0lUVURFLHk9TEFUSVRVREUsIGNvbG9yPSJwdXJwbGUiKSxkYXRhPXNpZGV3YWxrc19icm9va2x5biwgc2l6ZT0xKSsgc2NhbGVfZmlsbF92aXJpZGlzKCkrZ3VpZGVzKGNvbG9yPUZBTFNFKQ0KYGBgDQpUaGUgbWlzc2luZyBkYXRhIGluIFdpbGxpYW1zYnVyZyBpcyBoaW5kZXJpbmcgdGhpcyBhbmFseXNpcywgYnV0IHdlIGRvIHNlZSBob3cgdGhlIGF2ZXJhZ2UgaW5jb21lIGluIEJyb29rbHluIEhlaWdodHMsIFBhcmsgU2xvcGUsIGFuZCBSZWQgSG9vayBjb3JyZWxhdGUgd2l0aCBtb3JlIHNpZGV3YWxrIGNhZmVzLg0KDQpgYGB7ciBmaWcud2lkdGg9MTAsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpnZ21hcChxdWVlbnNfbWFwKStnZW9tX3BvbHlnb24oYWVzKGZpbGwgPSBpbmNvbWUuLCB4ID0gbG9uZywgeSA9IGxhdCwgZ3JvdXAgPSBncm91cCksIGRhdGEgPSBwbG90dGluZ196aXBzX3F1ZWVucywgYWxwaGEgPSAwLjcpK2dlb21fcG9pbnQoYWVzKHg9TE9OR0lUVURFLHk9TEFUSVRVREUsIGNvbG9yPSJwdXJwbGUiKSxkYXRhPXNpZGV3YWxrc19xdWVlbnMsIHNpemU9MSkrIHNjYWxlX2ZpbGxfdmlyaWRpcygpK2dlb21fdGV4dF9yZXBlbChkYXRhPXF1ZWVuc19uYW1lcywgYWVzKG1lYW5fbG9uLCBtZWFuX2xhdCwgbGFiZWw9TmVpZ2hib3Job29kKSxmb250ZmFjZT0iYm9sZCIsIHNpemU9MykrZ3VpZGVzKGNvbG9yPUYpDQpgYGANCkFzdG9yaWEgYW5kIExvbmcgSXNsYW5kIENpdHkgYXJlIGludGVyZXN0aW5nIGNhc2VzIHdpdGggcmVsYXRpdmVseSBsb3cgYXZlcmFnZSBpbmNvbWVzIGJ1dCBhIGhpZ2ggY29uY2VudHJhdGlvbiBvZiBzaWRld2FsayBjYWZlcy4gVGhlIG90aGVyIGFyZWEgdG8gdGhlIHNvdXRoLWVhc3QsIGFyb3VuZCBQYXJrc2lkZSBhbmQgRm9yZXN0IEhpbGxzLCBoYXMgYSBoaWdoIGNvbmNlbnRyYXRpb24gb2YgY2FmZXMgYW5kIGhpZ2ggaW5jb21lLg0KYGBge3IgZmlnLndpZHRoPTEwLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZ2dtYXAoYnJvbnhfbWFwKStnZW9tX3BvbHlnb24oYWVzKGZpbGwgPSBpbmNvbWUuLCB4ID0gbG9uZywgeSA9IGxhdCwgZ3JvdXAgPSBncm91cCksIGRhdGEgPSBwbG90dGluZ196aXBzX2Jyb254LCBhbHBoYSA9IDAuNykrZ2VvbV9wb2ludChhZXMoeD1MT05HSVRVREUseT1MQVRJVFVERSwgY29sb3I9InB1cnBsZSIpLGRhdGE9c2lkZXdhbGtzX2Jyb254LCBzaXplPTEpKyBzY2FsZV9maWxsX3ZpcmlkaXMoKStnZW9tX3RleHRfcmVwZWwoZGF0YT1icm9ueF9uYW1lcywgYWVzKG1lYW5fbG9uLCBtZWFuX2xhdCwgbGFiZWw9TmVpZ2hib3Job29kKSxmb250ZmFjZT0iYm9sZCIsIHNpemU9MykrZ3VpZGVzKGNvbG9yPUYpDQpgYGANCldpdGggdGhlIHZlcnkgbG93IG51bWJlciBvZiBCcm9ueCBkYXRhcG9pbnRzLCBpdCBpcyBub3QgZWFzeSB0byBzZWUgYSByZWFsdGlvbnNoaXAgYmV0d2VlbiBpbmNvbWUgYW5kIHdoZXJlIHRoZSBzaWRld2FsayBjYWZlcyBhcmUgbG9jYXRlZC4NCg0KYGBge3J9DQptZWFuX2luY29tZXMgPC0gcGxvdHRpbmdfemlwczEgJT4lIGdyb3VwX2J5KFpJUCkgJT4lIHN1bW1hcmlzZShtZWFuX2luY29tZT1tZWFuKGluY29tZS4pKQ0KemlwX3NpZGV3YWxrIDwtIHNpZGV3YWxrc19uYmggJT4lIGdyb3VwX2J5KFpJUCwgQk9ST1VHSCkgJT4lIHN1bW1hcmlzZShuX2NhZmVzPW4oKSkNCnN1bW1hcnlfY2FmZSA8LSBtZXJnZSh6aXBfc2lkZXdhbGssIG1lYW5faW5jb21lcywgYnk9IlpJUCIpDQpgYGANCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpnZ3Bsb3Qoc3VtbWFyeV9jYWZlLCBhZXMoeD1tZWFuX2luY29tZSwgeT1uX2NhZmVzKSkrZ2VvbV9wb2ludCgpK3hsYWIoIkF2ZXJhZ2UgSW5jb21lIChpbiBUaG91c2FuZHMgb2YgRG9sbGFycykiKSt5bGFiKCJOdW1iZXIgb2YgU2lkZXdhbGsgQ2FmZXMiKStnZW9tX3Ntb290aChtZXRob2Q9bG0pK3RoZW1lX2ZpdmV0aGlydHllaWdodCgpDQpgYGANCmBgYHtyfQ0KY2FmZS5sbSA9IGxtKG1lYW5faW5jb21lfm5fY2FmZXMsIGRhdGE9c3VtbWFyeV9jYWZlKSANCnN1bW1hcnkoY2FmZS5sbSkkci5zcXVhcmVkDQpgYGANCk9uIGFsbCB0aGUgZGF0YSwgdGhlcmUgaXMgcHJldHR5IHdlYWsgY29ycmVsYXRpb24gYmV0d2VlbiBpbmNvbWUgYW5kIG51bWJlciBvZiBzaWRld2FsayBjYWZlcywgd2l0aCBhbiByLXNxdWFyZXMgb2YgMC4xOTYuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpnZ3Bsb3Qoc3VtbWFyeV9jYWZlLCBhZXMoeD1tZWFuX2luY29tZSwgeT1uX2NhZmVzKSkrZ2VvbV9wb2ludCgpK3hsYWIoIkF2ZXJhZ2UgSW5jb21lIChpbiBUaG91c2FuZHMgb2YgRG9sbGFycykiKSt5bGFiKCJOdW1iZXIgb2YgU2lkZXdhbGsgQ2FmZXMiKStnZW9tX3Ntb290aChtZXRob2Q9bG0pK2ZhY2V0X3dyYXAofkJPUk9VR0gsIHNjYWxlcz0iZnJlZSIpK3RoZW1lX2ZpdmV0aGlydHllaWdodCgpDQpgYGANCg0KRmFjZXRpbmcgYnkgQm9yb3VnaCwgd2UgY2FuIHNlZSBob3cgdGhlIHJlbGF0aW9uc2hpcCBpcyBhY3R1YWxseSBuZWdhdGl2ZSBpbiBCcm9ueCBhbmQgUXVlZW5zLCBidXQgcG9zaXRpdmUgaW4gTWFuaGF0dGFuIGFuZCBCcm9va2x5biAod2l0aCB0aGUgc3Ryb25nZXN0IHJlbGF0aW9uc2hpcCBpbiBNYW5oYXR0YW4pLiANCg0KIyMjUHV0dGluZyBpdCBBbGwgVG9nZXRoZXINCk91ciBtYWluIGdyYXBocyBzdW1tYXJpemluZyB0aGUgZGF0YSBjYW4gYmUgc2VlbiBpbiB0aGUgRXhlY3V0aXZlIFN1bW1hcnkuIEhvd2V2ZXIsIHdlIGZvdW5kIHRoYXQgZGVuc2l0eSBjb3VudG91ciBtYXBzIG9mIGJvdGggb2YgdGhlIGxpcXVvciBkYXRhIGFuZCBzaWRld2FsayBjYWZlIGRhdGEgdGVsbCBhIHBvd2VyZnVsIHN0b3J5IGZyb20gMjAxNSB0byAyMDE3Lg0KDQpgYGB7cn0NCmxpcXVvcjwtcmVhZC5jc3YoIkRhdGEvbGlxdW9yX2xpY2Vuc2VzL21hc3NhZ2VkX2RhdGEuY3N2IikNCmxpcXVvcjwtbGlxdW9yICU+JSBmaWx0ZXIoQ2xhc3NpZmljYXRpb249PSJMSVFVT1IiKQ0KDQpoZHI8LWMoIkxPTkdJVFVERSIsIkxBVElUVURFIiwiWUVBUiIsIkJPUk9VR0giKQ0KDQpzaWRlPC1zaWRld2Fsa3NfbmJoICU+JSBmaWx0ZXIoU1VCTUlUX1lFQVI+MjAxNCAmIFNVQk1JVF9ZRUFSPDIwMTgpJT4lc2VsZWN0KExPTkdJVFVERSwgTEFUSVRVREUsU1VCTUlUX1lFQVIsIEJPUk9VR0gpDQpsaXE8LWxpcXVvciAlPiUgZmlsdGVyKGVmZmVjdGl2ZV95ZWFyPjIwMTQgJiBlZmZlY3RpdmVfeWVhcjwyMDE4KSU+JXNlbGVjdChMb25naXR1ZGUsIExhdGl0dWRlLGVmZmVjdGl2ZV95ZWFyLCBCT1JPVUdIKQ0KDQpjb2xuYW1lcyhsaXEpPC1oZHINCmNvbG5hbWVzKHNpZGUpPC1oZHINCg0Kc2lkZTwtc2lkZSU+JW11dGF0ZShUWVBFPSJTaWRld2FsayBDYWZlIikNCmxpcSA8LWxpcSAlPiUgbXV0YXRlKFRZUEU9IkJhciIpDQoNCg0KdG90YWwgPC0gcmJpbmQobGlxLHNpZGUpDQpgYGANCmBgYHtyIGZpZy53aWR0aD0yMCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmdnbWFwKG1hcCkrc3RhdF9kZW5zaXR5MmQoYWVzKHg9TE9OR0lUVURFLHk9TEFUSVRVREUsIGNvbG9yPVRZUEUpLHNpemU9MiwgYWxwaGE9LjUsY29udG91cj1UUlVFLGRhdGE9dG90YWwsIGdlb209ImRlbnNpdHkyZCIpK3hsYWIoIiIpICsgeWxhYigiIikrdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSxheGlzLnRpY2tzLng9ZWxlbWVudF9ibGFuaygpLGF4aXMudGlja3MueT1lbGVtZW50X2JsYW5rKCksYXhpcy5saW5lID0gZWxlbWVudF9saW5lKGNvbG9yID0gTkEpKSsgZ3VpZGVzKGZpbGw9RkFMU0UsYWxwaGE9RkFMU0UpK2ZhY2V0X3dyYXAofllFQVIpK3RoZW1lX2ZpdmV0aGlydHllaWdodCgpDQpgYGANCg0KVGhlIGdyYXBoIHNob3dzIGhvdyBpbiAyMDE1LCBtb3N0IGVzdGFibGlzaG1lbnRzIGFyZSBjb25jZW50cmF0ZWQgbW9zdGx5IGluIE1hbmhhdHRhbiwgd2l0aCBidWJibGVzIGluIEJyb29rbHluIEhlaWdodHMsIFdpbGxpYW1zYnVyZywgYW5kIEFzdG9yaWEuIEhvd2V2ZXIsIGluIDIwMTYsIHlvdSBjYW4gc2VlIHRoZSBiYXJzIGRlbnNpdHkgY29udG91cnMgc3ByZWFkIG91dCB0byBuZWlnaGJvcmhvb2RzIG5leHQgdG8gTWFuaGF0dGFuLCBlc3BlY2lhbGx5IGluIEJyb29rbHluLiBJbiAyMDE3LCB3ZSBhcmUgc2VlaW5nIHRoZSBzYW1lIGhhcHBlbiB3aXRoIHNpZGV3YWxrIGNhZmVzLg0KDQojIyMjIExpcXVvciBMaWNlbnNlIERhdGENCg0KRmlyc3QgSSBjaGVja2VkIHRoZSBkaXN0cmlidXRpb24gb2YgbGlxdW9yIGxpY2Vuc2VzIGJ5IHppcCBjb2RlLg0KDQoNCmBgYHtyfQ0KdGVtcCA8LSBueWNfbGlxdW9yX2xpY2Vuc2VzICU+JSBncm91cF9ieShtb2RfemlwKSAlPiUgc3VtbWFyaXNlKG51bSA9IG4oKSkNCnFwbG90KHRlbXAkbnVtLCBiaW53aWR0aCA9IDEwKSt0aGVtZV9maXZldGhpcnR5ZWlnaHQoKQ0KYGBgDQpJdCBpcyBub3Qgc3VycHJpc2luZyB0aGF0IHRoaXMgaXMgYSBsb25nIHRhaWxlZCBkaXN0cmlidXRpb24sIGhvd2V2ZXIgaXQgaXMgdmVyeSBzdXJwcmlzaW5nIHRoYXQgc29tZSB6aXAgY29kZXMgaGF2ZSB2ZXJ5IGhpZ2ggY29uY2VudHJhdGlvbnMgb2YgbGlxdW9yIGxpY2Vuc2VzLiBJIGxvb2tlZCBpbnRvIHRoZSB6aXAgY29kZXMgd2l0aCBlc3BlY2lhbGx5IGhpZ2ggbnVtYmVyIG9mIGxpY2Vuc2VzLg0KDQpgYGB7cn0NCm55Y19saXF1b3JfbGljZW5zZXMgJT4lIGdyb3VwX2J5KG1vZF96aXAsIG5iaCkgJT4lIHN1bW1hcmlzZShudW0gPSBuKCkpICU+JSBmaWx0ZXIobnVtID4gNDAwKQ0KYGBgDQpVbnN1cnByaXNpbmdseSwgMyBvZiB0aGVzZSA1IGhpZ2hseSBjb25jZW50cmF0ZWQgYXJlYXMgYXJlIGluIE1pZHRvd24uIE9uZSBpbnRlcmVzdGluZyB0aGluZyBJIGZvdW5kIHdoaWxlIGludmVzdGlnYXRpbmcgdGhlc2UgemlwIGNvZGVzIHdlcmUgdGhlIHNpbmd1bGFyIHByZW1pc2VzIHdpdGggdGhlIGhpZ2hlc3QgY29uY2VudHJhdGlvbnMuDQpgYGB7cn0NCm55Y19saXF1b3JfbGljZW5zZXMgJT4lIGdyb3VwX2J5KEFjdHVhbC5BZGRyZXNzLm9mLlByZW1pc2VzLi5BZGRyZXNzMS4pICU+JSBzdW1tYXJpc2UobnVtID0gbigpKSAlPiUgZmlsdGVyKG51bT4xNSkNCmBgYA0KQm90aCBvZiB0aGVzZSBhcmUgdHJhbnNwb3J0YXRpb24gaHVicy4gQXMgYSBmcmVxdWVudCBjb21tdXRlciBmcm9tIEdyYW5kIENlbnRyYWwsIEkgc2VlbWVkIHZlcnkgc3VycHJpc2VkIHRoYXQgdGhlcmUgd2VyZSAxMDEgcGxhY2VzIHRoZXJlIHRoYXQgc2VsbCBhbGNvaG9sLg0KDQpgYGB7cn0NCm55Y19saXF1b3JfbGljZW5zZXMgJT4lIGZpbHRlcihBY3R1YWwuQWRkcmVzcy5vZi5QcmVtaXNlcy4uQWRkcmVzczEuID09ICJHUkFORCBDRU5UUkFMIFRFUk1JTkFMIikgJT4lIGdyb3VwX2J5KFByZW1pc2VzLk5hbWUpICU+JSBzdW1tYXJpc2UobnVtID0gbigpKQ0KYGBgDQpUaGlzIHdhcyBldmVuIG1vcmUgc3VycHJpc2luZyB0byBtZSBhcyBJIGhhdmUgbmV2ZXIgYmVlbiBhYmxlIHRvIHB1cmNoYXNlIGFsY29ob2wgb24gTWV0cm8gTm9ydGguIFVwb24gZnVydGhlciBpbnNwZWN0aW9uDQpgYGB7cn0NCm55Y19saXF1b3JfbGljZW5zZXMgJT4lIGZpbHRlcihBY3R1YWwuQWRkcmVzcy5vZi5QcmVtaXNlcy4uQWRkcmVzczEuID09ICJHUkFORCBDRU5UUkFMIFRFUk1JTkFMIikgJT4lDQogIGZpbHRlcihQcmVtaXNlcy5OYW1lID09ICdNRVRSTyBOT1JUSCBDT01NVVRFUiBSQUlMUk9BRCcpICU+JSBncm91cF9ieShBZ2VuY3kuWm9uZS5PZmZpY2UuTmFtZSkgJT4lIA0KICBzdW1tYXJpc2UobnVtID0gbigpKQ0KYGBgDQoNCkFsYmFueSBpc3N1ZXMgYWxsIHRoZSBsaWNlbnNlcy4gSSBoYXZlIG5ldmVyIGJlZW4gb24gYSB0cmFpbiB0byBBbGJhbnksIGJ1dCBuZXh0IHRpbWUgSSBhbSwgSSB3aWxsIGRlZmluaXRlbHkgYXNrIGZvciBhbiBhZHVsdCBiZXZlcmFnZS4gDQoNClRoaXMgbWFkZSBtZSB3YW50IHRvIGtub3cgbW9yZSBhYm91dCB0aGUgZGlmZmVyZW50IHR5cGVzIG9mIGxpcXVvciBsaWNlbnNlcyB0aGF0IGFyZSBncmFudGVkLg0KDQpgYGB7ciwgZmlnLmhlaWdodD0xMH0NCm55Y19saXF1b3JfbGljZW5zZXMgJT4lICBnZ3Bsb3QoLiwgYWVzKHg9ZmN0X2luZnJlcShMaWNlbnNlLlR5cGUuTmFtZSkpKSArIGdlb21fYmFyKCkgKyBjb29yZF9mbGlwKCkgK3RoZW1lX2ZpdmV0aGlydHllaWdodCgpDQpgYGANCg0KVGhlcmUgYXJlIGEgbG90IG9mIGZyaW5nZSBjYXRlZ29yaWVzIHN1Y2ggYXMgRGlzdGlsbGVyIEItMSBhbmQgRGlzdGlsbGVyIEEgdGhhdCBhcmUgb2ZmZXJlZCB0byBhIGZldyBwbGFjZXMuIEhvd2V2ZXIsIHRoZSBhY3RpdmUgbGlxdW9yIGxpY2Vuc2VzIGFyZSB1bnN1cnByaXNpbmdseSBkb21pbmF0ZWQgYnkgb24tcHJlbWlzZSBsaXF1b3IgZXN0YWJsaXNobWVudHMsIHNvbWV0aW1lcyBrbm93biBhcyBiYXJzLiBUaGVyZSBpcyBhbHNvIGEgZGlzdGluY3Rpb24gYmV0d2VlbiBiZWVyIGFuZCB3aW5lLiBJIHdvdWxkIGxpa2UgdG8gZXhwbG9yZSB0aGF0IGZ1cnRoZXIuDQoNCkFub3RoZXIgYmFzaWMgdmFyaWFibGUgSSB3YW50ZWQgdG8gaW52ZXN0aWdhdGUgd2FzIHRoZSBob3cgZWZmZWN0aXZlIGxpcXVvciBsaWNlbmNlcyBoYXZlIGdyb3duIG92ZXIgdGhlIHllYXJzLiANCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCm55Y19saXF1b3JfbGljZW5zZXMgJT4lIGdyb3VwX2J5KGVmZmVjdGl2ZV95ZWFyKSAlPiUgc3VtbWFyaXNlKG51bSA9IG4oKSkgJT4lIA0KICBnZ3Bsb3QoLiwgYWVzKHg9ZWZmZWN0aXZlX3llYXIsIHkgPSBudW0pKSArIGdlb21fYmFyKHN0YXQ9J2lkZW50aXR5JykrdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkNCmBgYA0KVGhlIGJpZ2dlc3QgeWVhcnMgcmVwcmVzZW50ZWQgYXJlIDIwMTUgYW5kIDIwMTYuIEkgYW0gbm90IHN1cnByaXNlZCBieSB0aGUgZmFjdCB0aGF0IDIwMTcgaGFzIGZld2VyIHJlY29yZHMuIFRoZSB5ZWFyIGlzIG9ubHkgMS8zIGRvbmUsIGFuZCBJIGRvbid0IGtub3cgaG93IHF1aWNrbHkgaXQgdGFrZXMgYW4gYWN0aXZlIGxpcXVvciBsaWNlbnNlIHRvIGJlIHN1Y2Nlc3NmdWxseSByZWNvcmRlZCBzbyBpdCBtaWdodCBiZSBwb3NzaWJsZSB0aGF0IGxpcXVvciBsaWNlbnNlcyB0aGF0IGhhdmUgYmVlbiBncmFudGVkIGluIEZlYnJ1cmFyeSBhbmQgTWFyY2ggYXJlIG5vdCBpbmNsdWRlZCBpbiB0aGUgZGF0YXNldC4gSSBkbyB3YW50IHRvIGxvb2sgYSBsaXR0bGUgZnVydGhlciBhdCAyMDE0Lg0KDQpNeSBmaXJzdCBoeXBvdGhlc2lzIGlzIHRoYXQgb25seSBsaWNlbnNlcyBpbiBsYXRlciBtb250aHMgd2VyZSBpbmNsdWRlZA0KYGBge3J9DQpueWNfbGlxdW9yX2xpY2Vuc2VzICU+JSBtdXRhdGUobW9udGggPSBtb250aHMoTGljZW5zZS5FZmZlY3RpdmUuRGF0ZSkpICU+JSANCiAgbXV0YXRlKG1vbnRoX2luZCA9IG1vbnRoKExpY2Vuc2UuRWZmZWN0aXZlLkRhdGUpKSAlPiUgZ3JvdXBfYnkobW9udGgsIG1vbnRoX2luZCwgZWZmZWN0aXZlX3llYXIpICU+JSANCiAgc3VtbWFyaXNlKG51bSA9IG4oKSkgJT4lIGdncGxvdCguLCBhZXMoeCA9IHJlb3JkZXIobW9udGgsIC1tb250aF9pbmQpLCB5ID0gbnVtKSkgKyBnZW9tX2JhcihzdGF0PSdpZGVudGl0eScpICsNCiAgZmFjZXRfd3JhcCh+ZWZmZWN0aXZlX3llYXIpICsgY29vcmRfZmxpcCgpK3RoZW1lX2ZpdmV0aGlydHllaWdodCgpDQpgYGANCkFsdGhvdWdoIHRoaXMgZGF0YSB3b3VsZCBiZSBiZXR0ZXIgc2hvd24gaW4gYSB0aW1lLXNlcmllcyBwbG90LCB0aGUgcGF0dGVybiBpcyBjbGVhci4gVGhlIGRpc3RyaWJ1dG9uIG9mIGJhcnMgaW4gMjAxNCBieSBtb250aCBkb2VzIG5vdCBzdWdnZXN0IHRoYXQgbXkgaHlwb3RoZXNpcyBpcyBjb3JyZWN0IGFzIHRoZSBudW1iZXIgb2YgYWN0aXZlIGxpY2Vuc2VzIGZyb20gTWFyY2ggdG8gRGVjZW1iZXIgaW4gdGhhdCB5ZWFyIGlzIHJvdWdobHkgY29uc3RhbnQgYW5kIGFsbCBvdGhlciB5ZWFycyBvdGhlciB0aGFuIDIwMTcgc2hvdyB0aGF0IGxpY2Vuc2VzIHRoYXQgYXJlIGVmZmVjdGl2ZSBpbiBKYW51YXJ5IGFuZCBGZWJydWFyeSBhcmUgZmV3ZXIgaW4gY29tcGFyaXNvbiB0byBvdGhlciBtb250aHMuIEl0IG1pZ2h0IGJlIGNvaW5jaWRlbnRhbCwgYnV0IGluIDIwMTQtMjAxNiwgTWFyY2ggYW5kIE9jdG9iZXIgaGF2ZSBhIHNsaWdodGx5IGhpZ2hlciBudW1iZXIgb2YgbGljZW5zZXMgdGhhdCBiZWNvbWUgZWZmZWN0aXZlIHRoYXQgbW9udGguIA0KDQpJIHRoZW4gZGVjaWRlZCB0byBsb29rIGludG8gbGljZW5zZSB0eXBlLiANCg0KYGBge3J9DQpueWNfbGlxdW9yX2xpY2Vuc2VzICU+JSAgZmlsdGVyKGVmZmVjdGl2ZV95ZWFyID09IDIwMTQpICU+JSBnZ3Bsb3QoLiwgYWVzKHg9ZmN0X2luZnJlcShMaWNlbnNlLlR5cGUuTmFtZSkpKSArIGdlb21fYmFyKCkgKyBjb29yZF9mbGlwKCkrdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkNCmBgYA0KQXN0b25pc2hpbmdseSwgdGhlcmUgYXJlIHdheSBmZXdlciB0eXBlcyBvZiBsaWNlbnNlcyBpbiAyMDE0LiBBZGRpdGlvbmFsbHksIHRoZXJlIGFyZSBhbG1vc3Qgbm8gb24tcHJlbWlzZSBsaXF1b3IgbGljZW5zZXMuIE15IGd1ZXNzIGFzIHRvIHdoeSB0aGlzIGhhcHBlbnMgaXMgdGhhdCB0aGUgbGlxdW9yIGxpY2Vuc2VzIGZvciBiYXJzIGV4cGlyZSBxdWlja2VyLiBJIHdhbnRlZCB0byBsb29rIGludG8gaG93IGxvbmcgbGljZW5zZXMgYXJlIGVmZmVjdGl2ZSBmb3IuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp0ZW1wIDwtIG55Y19saXF1b3JfbGljZW5zZXMgJT4lIG11dGF0ZShhY3RpdmVfbGVuZ3RoID0gTGljZW5zZS5FeHBpcmF0aW9uLkRhdGUgLSBMaWNlbnNlLkVmZmVjdGl2ZS5EYXRlKQ0KcXBsb3QodGVtcCRhY3RpdmVfbGVuZ3RoLCBiaW53aWR0aCA9IDcpK3RoZW1lX2ZpdmV0aGlydHllaWdodCgpDQpgYGANCkVzc2VudGlhbGx5LCB0aGVyZSBzZWVtIHRvIGJlIHR3byBodWdlIHNwaWtlcyB3aGljaCBzZWVtIHRvIGNvcnJlc3BvbmQgdG8gMiBhbmQgMyB5ZWFycw0KDQpgYGB7cn0NCm55Y19saXF1b3JfbGljZW5zZXMgJT4lIG11dGF0ZShhY3RpdmVfbGVuZ3RoID0gTGljZW5zZS5FeHBpcmF0aW9uLkRhdGUgLSBMaWNlbnNlLkVmZmVjdGl2ZS5EYXRlKSAlPiUgDQogIGdyb3VwX2J5KGFjdGl2ZV9sZW5ndGgpICU+JSBzdW1tYXJpc2UobnVtID0gbigpKSAlPiUgYXJyYW5nZShkZXNjKG51bSkpICU+JSBoZWFkDQpgYGANCkJ5IGZhciwgdGhlIG1vc3QgY29tbW9uIGxlbmd0aHMgb2YgbGljZW5zZXMgYXJlIDczMCBhbmQgMTA5NSB3aGljaCBhcmUgMiBhbmQgMyB5ZWFycyByZXNwZWN0aXZlbHkuIExvb2tpbmcgYXQgdGhlIGdyYXBoLCB0aGUgcmVzdCBvZiB0aGUgbGVuZ3RocyBhcmUgY2xvc2UgdG8gdGhlc2UgbGVuZ3Rocw0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbnljX2xpcXVvcl9saWNlbnNlcyAlPiUgZmlsdGVyKExpY2Vuc2UuVHlwZS5OYW1lID09ICdPTi1QUkVNSVNFUyBMSVFVT1InKSAlPiUgDQogIG11dGF0ZShhY3RpdmVfbGVuZ3RoID0gTGljZW5zZS5FeHBpcmF0aW9uLkRhdGUgLSBMaWNlbnNlLkVmZmVjdGl2ZS5EYXRlKSAlPiUgDQogIHFwbG90KHggPSBhY3RpdmVfbGVuZ3RoLCBkYXRhID0gLiwgYmlud2lkdGggPSA3KSt0aGVtZV9maXZldGhpcnR5ZWlnaHQoKQ0KYGBgDQpJdCBkb2VzIGxvb2sgbGlrZSBvbi1wcmVtaXNlIGxpcXVvciBsaWNlbnNlcyBhcmUgZm9yIHR3byB5ZWFycy4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCm55Y19saXF1b3JfbGljZW5zZXMgJT4lIGZpbHRlcihMaWNlbnNlLlR5cGUuTmFtZSA9PSAnR1JPQ0VSWSBCRUVSLCBXSU5FIFBST0QnKSAlPiUgDQogIG11dGF0ZShhY3RpdmVfbGVuZ3RoID0gTGljZW5zZS5FeHBpcmF0aW9uLkRhdGUgLSBMaWNlbnNlLkVmZmVjdGl2ZS5EYXRlKSAlPiUgDQogIHFwbG90KHggPSBhY3RpdmVfbGVuZ3RoLCBkYXRhID0gLiwgYmlud2lkdGggPSA3KSt0aGVtZV9maXZldGhpcnR5ZWlnaHQoKQ0KYGBgDQpJbiBjb250cmFzdCwgZ3JvY2VyeSBsaWNlbnNlcyBsb29rIHRvIGJlIGZvciBhcm91bmQgMyB5ZWFycw0KDQpUaGUgbW9udGggcGxvdCBtYWRlIG1lIHF1ZXN0aW9uIGhvdyBsaWNlbnNlcyBiZWNvbWUgYWN0aXZlIGR1cmluZyB0aGUgbW9udGguDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpueWNfbGlxdW9yX2xpY2Vuc2VzICU+JSBtdXRhdGUoZG9tICA9IGRheShMaWNlbnNlLkVmZmVjdGl2ZS5EYXRlKSkgJT4lIHFwbG90KHg9ZG9tLCBkYXRhPS4pK3RoZW1lX2ZpdmV0aGlydHllaWdodCgpDQpgYGANCg0KQW5kIGZvciBleHBpcmluZyBkYXRlLA0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCm55Y19saXF1b3JfbGljZW5zZXMgJT4lIG11dGF0ZShkb20gID0gZGF5KExpY2Vuc2UuRXhwaXJhdGlvbi5EYXRlKSkgJT4lIHFwbG90KHg9ZG9tLCBkYXRhPS4pK3RoZW1lX2ZpdmV0aGlydHllaWdodCgpDQpgYGANCkZyb20gdGhlc2UgdHdvIHBsb3RzLCB3ZSBjYW4gc2VlIHRoYXQgbGljZW5zZXMgc2VlbSB0byBiZWNvbWUgZWZmZWN0aXZlIGF0IHRoZSBiZWdpbm5pbmcgb2YgdGhlIG1vbnRoIGFuZCBleHBpcmUgYXQgdGhlIGVuZCBvZiB0aGUgbW9udGgsDQoNCkkgYWxzbyB3YW50ZWQgdG8gbG9vayBhdCB0aGUgc3BhdGlhbCBkaXN0cmlidXRpb24uDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpnZ21hcChtYW5oYXR0YW5fbWFwKStzdGF0X2RlbnNpdHkyZChhZXMoeD1Mb25naXR1ZGUseT1MYXRpdHVkZSwgZmlsbD0uLmxldmVsLi4pLGRhdGE9bnljX2xpcXVvcl9saWNlbnNlcyU+JWZpbHRlcihlZmZlY3RpdmVfeWVhcj09IjIwMTUiKSAlPiUgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcihjaXR5LnggPT0gIk1hbmhhdHRhbiIpLCBnZW9tPSJwb2x5Z29uIiwgYWxwaGE9MC4zKStzY2FsZV9maWxsX2dyYWRpZW50KGxvdz0ieWVsbG93IixoaWdoPSJyZWQiKQ0KYGBgDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZ2dtYXAobWFuaGF0dGFuX21hcCkrc3RhdF9kZW5zaXR5MmQoYWVzKHg9TG9uZ2l0dWRlLHk9TGF0aXR1ZGUsIGZpbGw9Li5sZXZlbC4uKSxkYXRhPW55Y19saXF1b3JfbGljZW5zZXMlPiVmaWx0ZXIoZWZmZWN0aXZlX3llYXI9PSIyMDE2IikgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIoY2l0eS54ID09ICJNYW5oYXR0YW4iKSwgZ2VvbT0icG9seWdvbiIsIGFscGhhPTAuMykrc2NhbGVfZmlsbF9ncmFkaWVudChsb3c9InllbGxvdyIsaGlnaD0icmVkIikNCmBgYA0KSW4gdGhlc2UgdHdvIG1hcHMgd2Ugc2VlIHRoZSBzcGF0aWFsIGRpc3RyaWJ1dGlvbiBiZXR3ZWVuIHR3byB5ZWFycyBpbiB3aGljaCB0aGUgbGljZW5zZSBiZWNhbWUgZWZmZWN0aXZlLiBUaGV5IGFyZSBib3RoIHZlcnkgc2ltaWxhciB3aXRoIHRoZSBNaWR0b3duIGFuZCBMb3dlciBFYXN0IFNpZGUgaGF2aW5nIHRoZSBoaWdoZXN0IGRlbnNpdHkuIFRoZSBiaWdnZXN0IGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgdHdvIGlzIHRoYXQgTW9ybmluZ3NpZGUgaGVpZ2h0cyBhbmQgbm9ydGggaGFzIG1vcmUgYWN0aXZpdHkgaW4gMjAxNi4gQWdhaW4sIHRoaXMgaXNuJ3QgdG8gc2F5IHRoYXQgbmV3IHBsYWNlcyBwb3BwZWQgdXAgaW4gdGhlc2UgbmVpZ2hib3Job29kcywgYnV0IGxpY2Vuc2VkIGJlY2FtZSBhY3RpdmUgaW4gdGhpcyB5ZWFyLiANCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpnZ21hcChtYW5oYXR0YW5fbWFwKStzdGF0X2RlbnNpdHkyZChhZXMoeD1Mb25naXR1ZGUseT1MYXRpdHVkZSwgZmlsbD0uLmxldmVsLi4pLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YT1ueWNfbGlxdW9yX2xpY2Vuc2VzJT4lIGZpbHRlcihjaXR5LnggPT0gIk1hbmhhdHRhbiIpICU+JSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKExpY2Vuc2UuVHlwZS5OYW1lID09ICJFQVRJTkcgUExBQ0UgQkVFUiIpLCBnZW9tPSJwb2x5Z29uIiwgYWxwaGE9MC4zKStzY2FsZV9maWxsX2dyYWRpZW50KGxvdz0ieWVsbG93IixoaWdoPSJyZWQiKQ0KYGBgDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZ2dtYXAobWFuaGF0dGFuX21hcCkrc3RhdF9kZW5zaXR5MmQoYWVzKHg9TG9uZ2l0dWRlLHk9TGF0aXR1ZGUsIGZpbGw9Li5sZXZlbC4uKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGE9bnljX2xpcXVvcl9saWNlbnNlcyU+JSBmaWx0ZXIoY2l0eS54ID09ICJNYW5oYXR0YW4iKSAlPiUgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcihMaWNlbnNlLlR5cGUuTmFtZSA9PSAiUkVTVEFVUkFOVCBXSU5FIiksIGdlb209InBvbHlnb24iLCBhbHBoYT0wLjMpK3NjYWxlX2ZpbGxfZ3JhZGllbnQobG93PSJ5ZWxsb3ciLGhpZ2g9InJlZCIpDQpgYGANClRoZXNlIHR3byBtYXBzIGNvbXBhcmUgd2luZSBpbiByZXN0YXVyYW50cyB0byBiZWVyIGluIGVhdGluZyBwbGFjZXMuIEluIHRoZSB0b3AgbWFwLCB0aGUgaG90c3BvdCBmb3IgYmVlciBzdHJldGNoZXMgZnJvbSBNaWR0b3duIGFsbCB0aGUgd2F5IGRvd24gdG8gdGhlIExvd2VyIEVhc3QgU2lkZS4gQWRkaXRpb25hbGx5LCBvdXRzaWRlIG9mIHRoZSB1cHBlciBlYXN0IHNpZGUgYW5kIHVwcGVyIHdlc3Qgc2lkZSwgdGhlIG1hcCBpcyBwcmV0dHkgbXVjaCBjb3ZlcmVkLiBJbiBjb250cmFzdCwgZm9yIHdpbmUsIHRoZSBjb25jZW50cmF0aW9uIGlzIGNsZWFybHkgY29uY2VudHJhdGVkIGluIHRoZSBTb3V0aCBhbmQgdGhlIEVhc3QuIEZ1cnRoZXJtb3JlLCB0aGUgYXJlYXMgbm90IGNvdmVyZWQgaW4gdGhlIGJlZXIgbWFwIGFyZSBjb3ZlcmVkIGluIHRoZSB3aW5lIG1hcCBvdXRzaWRlIG9mIENlbnRyYWwgUGFyay4gVGhlc2UgYXJlYXMgdGVuZCB0byBiZSBtb3JlIGFmZmx1ZW50LCBzbyB0aGlzIGdpdmVzIHNvbWUgZXZpZGVuY2UgdGhhdCB3aW5lIGlzIGVuam95ZWQgbW9yZSBieSBtb3JlIGFmZmx1ZW50IHBlb3BsZS4gDQojIyA2LiBDb25jbHVzaW9uICANClRoaXMgcHJvamVjdCBkZWx2ZWQgaW50byBleHBsb3Jpbmcgd2hlcmUgc2lkZXdhbGsgY2FmZSBhbmQgbGlxdW9yIGxpY2Vuc2VzIGFyZSBpc3N1ZWQgYW5kIHRoZSBnZW9zcGF0aWFsIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZXNlIHZhcmlhYmxlcyBhbmQgaW5jb21lLiBJbnR1aXRpdmVseSwgYXJlYXMgZ2VuZXJhbGx5IGFzc29jaWF0ZWQgYXMgdXAgYW5kIGNvbWluZyBuZWlnaGJvcmhvb2RzIHN1Y2ggYXMgV0lsbGlhbXNidXJnIHdlcmUgZm91bmQgdG8gc2VlIGluY3JlYXNlcyBpbiB0aGUgcGFzdCB0d28geWVhcnMuIEhvd2V2ZXIsIGluIFF1ZWVucywgdGhlcmUgd2FzIGZvdW5kIHRvIGJlIGEgbmVnYXRpdmUgY29ycmVsYXRpb24gYmV0d2VlbiBpbmNvbWUgYW5kIHNpZGV3YWxrIGNhZmUgYW5kIGxpcXVvciBsaWNlbnNlcy4gIA0KDQpBbGwgb2YgdGhlc2UgY29uY2x1c2lvbnMgc2hvdWxkIGJlIHRha2VuIHdpdGggYSBncmFpbiBvZiBzYWx0LiBGaXJzdCwgd2UgY2FuJ3QgYmUgc3VyZSB0aGF0IHRoZSBkYXRhIGZvciBlaXRoZXIgc2lkZXdhbGsgY2FmZXMgb3IgbGlxdW9yIGxpY2Vuc2VzIGlzIGNvbXBsZXRlLiBUaGVyZSBtaWdodCBiZSBwbGFjZXMgd2hvIG9wZXJhdGUgd2l0aG91dCBhIGxpY2Vuc2Ugb3IgcGxhY2VzIHRoYXQgYXJlbid0IHJlY29yZGVkIHByb3Blcmx5IGluIHRoZSBkYXRhc2V0LiBPbmUgc3VjaCBleGFtcGxlIHdhcyBmb3VuZCBpbiB0aGUgbGlxdW9yIGxpY2Vuc2UgZGF0YS4gQWRkaXRpb25hbGx5LCBvbmx5IHR3byBsaXF1b3IgbGljZW5zZXMgd2VyZSBncmFudGVkIHRvIG5vbiByYWlsLWNhcnMgaW4gR3JhbmQgQ2VudHJhbC4gVGhpcyBzZWVtcyB0byBiZSB1bmRlcmVzdGltYXRpbmcgdGhlIHRydWUgdmFsdWUuIFRoZXJlIHdlcmUgYWxzbyB0d28gbWlzc2luZyB6aXAgY29kZXMgaW4gdGhlIGRhdGEgYW5kIDE1IHppcCBjb2RlcyB0aGF0IHdlcmUgbm90IGZvdW5kIGluIHRoZSBsaXF1b3IgbGljZW5zZSBkYXRhLCBnaXZpbmcgZnVydGhlciBldmlkZW5jZSB0aGF0IHRoaXMgZGF0YXNldCB3YXMgaW5jb21wbGV0ZS4gIA0KDQpUaGUgaW5jb21lIGRhdGEgd2FzIGNvbGxlY3RlZCBieSB0aGUgY2Vuc3VzLiBXaGlsZSB0aGlzIGRhdGEgc2hvdWxkIGJlIGhpZ2ggcXVhbGl0eSwgdGhlIG1vc3QgcmVjZW50IGNlbnN1cyB3YXMgY29uZHVjdGVkIGluIDIwMTAgd2hpY2ggbWVhbnMgZGF0YSBtaWdodCBub3QgYmUgcmVjZW50LCBlc3BlY2lhbGx5IGNvbnNpZGVyaW5nIHRoZSBhbmFseXNpcyBmb2N1c2VkIG9uIHRoZSB5ZWFycyAyMDE1LTIwMTYuICBBbHNvLCB3ZSB1c2VkIHdoZW4gdGhlIGxpY2Vuc2Ugd2FzIGlzc3VlZCBhcyBhIHByb3h5IGZvciB3aGljaCBuZWlnaGJvcmhvb2RzIHdlcmUgZ3Jvd2luZy4gSW4gdGhlIHNpZGV3YWxrIGNhZmUgZGF0YSwgcmVuZXdlZCBsaWNlbnNlcyBvdmVyd3JvdGUgdGhlIGV4aXN0aW5nIGxpY2Vuc2UgZm9yIHRoZSBwbGFjZS4gSW4gdGhlIGxpcXVvciBsaWNlbnNlIGRhdGEsIGl0IHdhcyBzZWVuIHRoYXQgbGljZW5zZXMgaGFkIHRvIHJlbmV3ZWQgZXZlcnkgdHdvIHllYXJzIGZvciBiYXJzLiBXaXRob3V0IGFjY2VzcyB0byBhbGwgdGhlIGhpc3RvcmljYWwgZGF0YSBpdCBpcyBoYXJkIHRvIHNlcGFyYXRlIGdyb3d0aCBmcm9tIHRoZSByZW5ld2FsIG9mIGxpY2Vuc2VzLiAgDQoNCkluIHRlcm1zIG9mIGNvcnJlbGF0aW9uLCBzb21lIG5laWdoYm9yaG9vZHMgc3VjaCBhcyBNSWR0b3duIHdlcmUgZm91bmQgdG8gaGF2ZSBhIGhpZ2ggbnVtYmVyIG9mIGxpcXVvciBsaWNlbnNlcyBhbmQgc2lkZXdhbGsgY2FmZXMuIFRoZXNlIGNvcnJlbGF0aW9ucyBtaWdodCBiZSBzcHVyaW91cyBhcyBtb3JlIHBlb3BsZSB0ZW5kIHRvIGxpdmUgaW4gdGhlc2UgYXJlYXMuIFRoaXMgYW5hbHlzaXMgb25seSBmb2N1c2VkIG9uIGhvdyBpbmNvbWUgYWZmZWN0ZWQgc2lkZXdhbGsgY2FmZXMgYW5kIGJhcnMgcG9wcGluZyB1cCwgYnV0IHRoZSByZWFsIGNhdXNlIG1pZ2h0IGJlIHNvbWV0aGluZyB3ZSBkaWRuJ3QgYWNjb3VudCBmb3Igc3VjaCBhcyBkZW1vZ3JhcGhpYyBpbmZvcm1hdGlvbiwgZWFzZSBvZiB0cmFuc3BvcnRhdGlvbiwgZXRjLiAgIA0KDQpGb3IgYW4gaW50cm9kdWN0b3J5IHN0dWR5LCB3ZSB3ZXJlIGFibGUgdG8gaWRlbnRpZnkgc2V2ZXJhbCB0cmVuZHMgaW4gaG93IGxpY2Vuc2VzIGFyZSBpc3N1ZWQsIGhvdyB3ZWFsdGggaXMgZGlzdHJpYnV0ZWQgZ2VvZ3JhcGhpY2FsbHkgYWNyb3NzIE5ldyBZb3JrIENpdHksIGFuZCBjb3JyZWxhdGlvbnMgYmV0d2VlbiB0aGVzZSB0d28gdmFyaWFibGVzLiBXaXRob3V0IGRvaW5nIGFueSBtb2RlbGxpbmcsIHdlIHdlcmUgYWJsZSB0byBnZW5lcmF0ZSBhbmQgYXNzZXNzIHRoZSB2YWxpZGl0eSBvZiBzZXZlcmFsIGh5cG90aGVzZXMsIGFuZCBzb21lIG9mIG91ciBwcmVjb25jZWl2ZWQgbm90aW9ucyB3ZXJlIHByb3ZlbiB3cm9uZy4gSG9wZWZ1bGx5LCB0aGlzIHN0dWR5IHdhcyBhYmxlIHRvIHNob3cgcHJvbWlzZSBpbiBob3cgdmlzdWFsaXppbmcgYW5kIG1hcHBpbmcgbGljZW5zZXMgY2FuIGhlbHAgcHJvdmlkZSBpbnNpZ2h0IGFib3V0IHRoZSB3aGF0IGRyaXZlcyBjZXJ0YWluIGluc3RpdHV0aW9ucyB0byBncm93LiA=